Redis( 缓存篇 ==> 互斥锁解决缓存击穿

我们可以在查询缓存未命中的时候添加一个互斥锁。这样一来,在面对高并发的情况下,只有第一个进来的线程才可以拿到锁然后操作数据库,待操作结束后释放锁,未拿到锁的用户则等待一段时间重新查询缓存,直到缓存重建完毕后拿到数据后方可结束。

 关于互斥锁这一部分,我们可以使用Redis里的setnx命令来模拟实现。

setnx命令对应java里的setIfAbsent,代码如下:

这样一来,只有第一个进来的线程才可以添加key并返回true,后面来的线程无法完成添加且返回false。

我们在写一个释放锁的方法,在拿到锁的进程操作数据库结束后,把锁释放掉(防止死锁)

总体实现流程大致如下:

  1. 根据key查询Redis缓存
  2. 命中了数据直接返回即可
  3. 命中了空值直接返回错误信息(防止缓存穿透)
  4. 获取锁
  5. 获取锁失败则等待一段时间后重新查询缓存
  6. 获取锁成功则查询数据库
  7. 数据库查询为null则缓存一个空值到Redis里(防止缓存穿透),过期时间设置短一点
  8. 数据库查询成功则直接添加缓存到Redis里
  9. 释放锁

 

代码实现如下(带详细注释):

    public Shop cacheBreakDown(Long id) {
        //定义一个ID
        String cacheID = CACHE_SHOP_KEY + id;
        //1、查询Redis
        String shopJson = stringRedisTemplate.opsForValue().get(cacheID);
        //2、判断是否命中
        if(StrUtil.isNotBlank(shopJson)){
            //命中了直接返回
            return BeanUtil.toBean(shopJson,Shop.class);
        }
        //3、判断是否命中空值
        if(shopJson != null){
            //命中了空值,直接返回
            return null;
        }
        //提前定义一个shop,要不在try里跨作用域了最后return不了
        Shop shop = null;
        try{
            //4、缓存未命中,获取锁,判断是否成功拿到锁
            if(!getLock(id)){
                //没有拿到锁,说明已有别的线程进来了,正在添加缓存
                //休眠一会,重新查询缓存
                Thread.sleep(200);
                //一定要return!递归出口
                return cacheBreakDown(id);
            }
            //5、只有拿到锁的线程才可以查询数据库
            shop = getById(id);
            //模拟缓存重建过程所需时间
            Thread.sleep(500);
            //5、判断是否查询到数据
            if(shop == null){
                //没数据,返回空值缓存,防止缓存击穿
                stringRedisTemplate.opsForValue().set(cacheID,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            //6、查询成功,添加缓存
            stringRedisTemplate.opsForValue().set(cacheID,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        }catch (Exception e){
            //统一异常处理
            throw new RuntimeException(e);
        }finally {
            //7、释放锁
            unLock(id);
        }
        //8、返回
        return shop;
    }

总结一下互斥锁的优缺点:

优点:

  • 不需要消耗额外内存
  • 实现简单
  • 保证了一致性

缺点:

  • 没拿到锁的线程需要等待重试,效率不高
  • 加锁和解锁的过程没有保证原子性,可能面临死锁的风险

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在处理Redis缓存击穿问题时,可以采用互斥锁的方式进行解决互斥锁是一种机制,用于保护共享资源的访问,确保同一时刻只有一个线程能够获取锁来执行操作。在Redis缓存击穿的场景中,当一个高并发的请求发生时,如果缓存中不存在所需的数据,那么这个请求就会触发数据库的查询操作。 针对这种情况,我们可以引入互斥锁的概念来解决Redis缓存击穿问题。具体做法是,在查询缓存之前,先尝试获取互斥锁。如果获取到了锁,表示当前线程是第一个发起请求的线程,它可以继续执行查询数据库的操作,并将结果写入缓存。而其他线程在获取锁之前,只能等待一段时间后再次尝试获取锁。 为了实现这个逻辑,我们可以定义获取互斥锁的方法和释放锁的方法。获取互斥锁的方法可以使用Redis提供的setIfAbsent()方法来进行操作,设置一个键值对,如果该键不存在,则设置成功,并返回true;否则,返回false。释放锁的方法则是删除相应的键值对。 通过使用互斥锁,能够在高并发的情况下有效地控制对数据库的访问,避免出现缓存击穿的问题。这种方案在逻辑上实现了对缓存穿透和击穿的处理,并且可以封装为一个工具类,适用于各种场景。 引用内容: 代码片段,定义了获取互斥锁和释放锁的方法 Redis缓存穿透和击穿的处理方案 描述了使用互斥锁解决缓存击穿问题的过程 提到了缓存击穿问题的本质和影响,引用了一个Redis教程中的PPT内容<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白日日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值