Redis缓存穿透、雪崩、击穿以及分布式锁和本地锁


前言

先说下数据库的本质仍然还是基于磁盘来交互的,用过HDFS的都知道基于磁盘交互在单线程的情况下还是快的,在多线程情况下,速度呈倍数下降。所以在系统中redis正是诞生出来解决这个问题的。


一、缓存穿透

举个例子:比如说某位老哥开发了一个网站,然后这个网站非常的受欢迎,某一天突然遭到了黑客疯狂的攻击,他的这个攻击手段就是采用这个缓存穿透的原理。

大家都知道通常情况下,数据库的主键是从0开始递增的,是没有负数的,那么这位黑客就利用这点,他不断的用 ID 小于零的参数发请求过来。这位老哥刚开始是把网站的所有数据放到了 redis 缓存里面去,但是黑客是用 ID 小于零的数来请求,redis 缓存里面并没有这个 ID 小于零的数据,这样 redis 就查不到这个结果,一旦 redis 查不到结果就会去数据库中查,那么所有请求都会打到数据库,而且会一直打到数据库中去,因为 redis 缓存这层根本拦截不到这样的数据。
在这里插入图片描述
总而言之,缓存穿透就是来查找一个不存在的数据,从而避免了redis,大量的访问会直接到达数据库使得数据库直接宕机。

解放方案

  • 不管数据库有没有查找到相应的数据,尽管是null为空值,都将此值写会redis
  • 对请求的参数做合法性校验
  • 使用过滤器来过滤请求

代码

if(list == null){
// key value 有效时间时间单位
    redisTemplate . opsForValue( ). set( navKey,null,10, T imeUnit. MINUTES );
}else{
    redisTemplate . opsForValue( ). set( navKey, result,7 ,TimeUnit. DAYS);
}

二、缓存雪崩

缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。简而言之就是存在redis中的所有的数据在同一时刻消失了,这里可能有人说为什么一定要设置消失时间,我们都知道缓存是存在于内存之中的,如果一直叠加而不能GC垃圾回收,那么系统会报出oom的错误,所以这一步必不可少。
在这里插入图片描述
解决方案

  • 随机设置这个缓存的失效时间,不让大量的 key 在同一时间失效,即在设置这个缓存的时候,可以将 key 的失效时间分散开。
  • 数据库有限流方案,当达到了限流设置的参数,那么就会拒绝请求,从而保护了后台db。
  • 加锁排队
    加锁排队代码
//伪代码
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    String lockKey = cacheKey;

    String cacheValue = CacheHelper.get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    } else {
        synchronized(lockKey) {
            cacheValue = CacheHelper.get(cacheKey);
            if (cacheValue != null) {
                return cacheValue;
            } else {
              //这里一般是sql查询数据
                cacheValue = GetProductListFromDB(); 
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
        }
        return cacheValue;
    }
}

三、缓存击穿

大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。于是就会导致: 在缓存失效瞬间,有大量线程构建缓存,导致后端负载加剧,甚至可能让系统崩溃。这么说可能有一点模糊,举个例子,比如华为mate50刚上线,这时候因为查询存入了缓存中,但是当某一刻缓存自动失效之后,大量的访问就会直接越过redis。
在这里插入图片描述
解决方案

  • 加锁,只能加锁,加同步锁或者分布式锁

加锁伪代码


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;      
   }
 }

四、本地锁和分布式锁

1、本地锁

本地锁也叫线程锁
我们都知道springboot是单进程模式,而进程又是由多个线程组成,那么线程锁的意思就是在这个进程中(代码段)只有一个线程可以通过,只有一个坑位。

2、分布式锁

先来看图片
在这里插入图片描述
这里每一个商品服务代表一个节点,由这样8个节点组成的集群,每一个节点上面都会开启商品服务(springboot),所以就会有8个进程,如果使用本地锁(线程锁)那么仍然会有8个请求到达我们的缓存或者数据库。
而分布式锁就是来避免这种情况,虽然8个请求并不多,且压力不大,但是想要锁住所有的进程只能使用分布式锁。
一般我们使用Redisson来操作分布式锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值