缓存穿透,缓存击穿,缓存雪崩、大Key

缓存穿透

概述

缓存穿透是指查询一个数据库一定不存在的数据。当业务系统需要查询的数据根本在数据库中不存在时,每一次的查询请求都会最终对数据库造成一次查询,这种情况称为缓存穿透,即业务访问根本不存在的数据。

当查询一个实际不存在的数据,请求一定会查询数据库,可以利用这个漏洞,高并发大量访问,以此对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的Key进行攻击

解决思路:

缓存空数据,即如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。下次请求一定会被缓存拦截。

使用BloomFilter,即在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key。当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
 

缓存击穿

概述

缓存击穿是指缓存中没有但数据库中有的数据(一般是由于缓存时间到期,大量请求直接访问数据库),如一个Key非常热点,短时间内集中大量并发,当这个缓存Key在失效的瞬间,持续的并发请求就会直接请求数据库,大量的并发请求可能会瞬间把DB压垮(比如电商秒杀项目中,某件爆款货物)

解决思路

设置热点数据用不过期:从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

使用互斥锁解决问题

使用互斥锁解决,伪代码如下:

public String getData(String key){
        String result = getDataFromRedis(key);
        try {
            if(result == null){
                if(redisLock.tryLock()){
                    result = getDataFromDB(key);
                    if(result != null){
                        putRedis(key,result);
                    }
                }
            }else{
                Thread.sleep(100);
                getData(key);
            }
        }catch (Exception e){

        }finally {
            redisLock.unLock();
        }
        return result;
    }


public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
		  if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                      get(key);  //重试
              }
          } else {
              return value;      
          }
 }

缓存雪崩

概述

缓存雪崩是指缓存中数据大批量到过期时间或者缓存失效(Redis宕机等),此时的巨大查询请求将直接访问数据库,引起数据库压力过大甚至down机。

和缓存击穿不同的是: 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都缓存都查不到从而查数据库。是指在某一个时间段,缓存集中过期失效。

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

解决思路

避免集中过期:一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源

使用缓存集群:缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

热点数据均匀分布:如果缓存数据库是分布式,可以将热点数据均匀随机分布在不同的缓存数据库中,对于热点数据可以设置不过期等

热点Key

概述

高频访问的Key,比如秒杀的信息,超高并发可能直接导致Redis直接宕机,

  1. 读热点Key问题:读流量集中到某key,导致指定缓存机器压力过大
  2. 写热点Key问题:缓存失效时,大量线程穿透构建缓存,带来db和服务压力。

解决方案

  1. 将缓存在分布式服务机器做二次缓存
  2. 备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。
  3. 限流熔断保护。
  4. 写缓存问题(缓存击穿)
    1. 使用互斥锁(mutex key),只让一个线程构建缓存,其他线程等待(自旋)构建缓存的线程执行完,重新从缓存获取数据就可以了
    2. Redis上不设置过期时间,过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期。对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据

Redis大Key

概述

主要包括以下两种情况的key,称之为Redis的大Key

  1. 单个key 存储的 value 很大
  2. hash, set,zset,list 结构中存储过多的元素(如list的元素存储了几十万个)

Redis大Key带来的问题

由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间、IO性能造成影响,从而降低了Redis的整体QPS,具体如下:

  1. 大Key数据读写内存占用多、响应时间长,响应整体读写性能,甚至阻塞,或者OOM等
  2. 集群模式在slot分片均匀状况下,会出现数据和查询倾斜状况,部分有大key的Redis节点占用内存多,QPS高
  3. 大key相关的删除或者自动过期时,会降低QPS,极端状况下,会形成主从复制异常,Redis服务阻塞没法响应请求。

如何找出 Redis 大 key

  1. 使用redis-cli -h IP -p port –bigkeys命令,该命令会列出各个类型数据中大Key中的最大的那个key的信息。
  2. 使用华为云管理控制台缓存分析-大Key分析工具。执行完成后,查看信息即可。具体使用方法参考以下链接:https://support.huaweicloud.com

Redis中大Key的解决方法

对Redis中大Key的解决思想就是分治,将大key拆成小的部分,以便能够将大Key的数据读写分摊到多个Redis实例上,比如:

  • 对于需要整取 value 的 大 key, 可以尝试将对象分拆成几个 key-value, 使用 multiGet 获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个实例中,降低对单个实例的IO影响
  • 对于每次需要取部分 value 的 大 key, 同样可以拆成几个 key-value,也可以将这些存储在一个 hash 中,每个 field 代表具体属性,使用 hget,hmget 来获取部分 value,使用 hset,hmset 来更新部分属性;
  • 对于 value 中存储过多元素的 key(如list、set等数据结构), 同样可以将这部分元素拆分,可以对存储元素按一定规则进行分类,分散存储到多个Redis实例中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值