Spring Boot使用互斥锁解决Redis缓存击穿问题

一、什么是缓存击穿?

缓存击穿问题也叫热点key问题,一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大的冲击

二、解决方案

1.互斥锁

本文采用互斥锁的方式来解决缓存击穿问题。那么什么是互斥锁呢?
如下图:
在这里插入图片描述

当线程1请求过来时,先查询缓存未命中就会开启互斥锁,如果这时候线程2进来会先查询缓存,未命中也会开启互斥锁,但是线程1已经开启了会获取锁失败,那么会休眠,稍后再去获取缓存。线程1在获取锁成功之后会查询数据库重建缓存,将数据存入redis中最后再释放锁。
互斥锁与悲观锁有点类似,可以参考悲观锁来理解。

在Redis中可以使用setnx命令来实现互斥锁,当lock存在的时候,无法进行设值
在这里插入图片描述

2.编码实现

准备两个方法,获取锁与释放锁
获取锁

	//尝试获取锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

释放锁

	//释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

互斥锁解决缓存击穿

    **
    * 根据id查询商铺信息
    * @param id
    * @return
    */
   @Override
   public Result queryById(Long id) {
       Shop shop = null;
       try {
           //1.从redis获取缓存
           String jsonStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);

           //2.判断缓存是否命中
           if (StrUtil.isNotBlank(jsonStr)){
               //命中,返回
               return Result.ok(JSONUtil.toBean(jsonStr, new TypeReference<Shop>() {
               },true));
           }

           //3.判断命中的是否是空字符串
           if (jsonStr != null){
               return Result.fail("商铺信息不存在");
           }

           //未命中
           //4.获取互斥锁
           boolean isLock = tryLock(LOCK_SHOP_KEY + id);

           //5.判断是否获取锁成功
           if (!isLock){
               //6.失败,休眠
               Thread.sleep(300);
               //7.重试,递归
               return queryById(id);
           }

           //7.根据id查询数据库
           shop = this.getById(id);

           //8.判断shop是否存在
           if (ObjectUtil.isNull(shop)){
               //不存在,将空值存入redis,返回错误信息
               stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
               return Result.fail("商铺信息不存在");
           }
           //存在,写入redis
           stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }finally {
           //9.释放互斥锁
           unlock(LOCK_SHOP_KEY + id);
       }

       return Result.ok(shop);
}

总结

使用互斥锁优点是没有额外的内存消耗,保证一致性,实现简单。但是也存在性能问题,每个没有获取到锁的线程都会等待有死锁的风险。
还有一种解决办法是使用逻辑过期的方法这个就解决了线程等待问题,但是这个就不保证数据的一致性,会得到过期的数据。怎样选择还是得看具体的业务场景。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值