黑马redis基于互斥锁解决缓存击穿问题一些理解

/**互斥锁实现解决缓存击穿**/
public Shop queryWithMutex(Long id){
    //1.从Redis内查询商品缓存
    String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
    if(StrUtil.isNotBlank(shopJson)){
        //手动反序列化
        return JSONUtil.toBean(shopJson, Shop.class);
    }
    //如果上面的判断不对,那么就是我们设置的""(有缓存"",证明数据库内肯定是没有的)或者null(没有缓存)
    //判断命中的是否时空值
    if(shopJson!=null){//
        return null;
    }

    //a.实现缓存重建
    //a.1 获取互斥锁
    String lockKey = LOCK_SHOP_KEY + id;
    Shop shop = null;
    try {
        boolean hasLock = tryLock(lockKey);
        //a.2 判断是否获取到,获取到:根据id查数据库 获取不到:休眠
        if(!hasLock){
            Thread.sleep(50);
            return queryWithMutex(id);
        }

        //2.不存在就根据id查询数据库
        shop = getById(id);
        //模拟重建的延时
        Thread.sleep(200);
        if(shop==null){
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        //3.数据库数据写入Redis
        //手动序列化
        String shopStr = JSONUtil.toJsonStr(shop);
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,shopStr,CACHE_SHOP_TTL, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        //释放互斥锁
        unlock(lockKey);
    }

    return shop;
}

中 if(shopJson!=null){// return null; }这里为什么返回null

这段代码的目的是使用互斥锁的方式来解决缓存击穿的问题。缓存击穿指的是当缓存中没有所需数据时,如果有大量并发请求同时到达,它们都会去数据库加载数据,从而对数据库造成很大压力。

在代码中,如果从 Redis 获取到了数据,就直接返回数据。如果得到的是空字符串(""),说明之前有查询过数据库,但是没有查到数据,所以在缓存中故意存了一个空字符串以表示这个情况。因此,当再次碰到这个空字符串时,就可以直接返回 null,不需要再去数据库查询一次了。

这里的 if(shopJson!=null) 之后返回 null 的逻辑是这样的:如果 shopJson 是一个非空字符串,就表示缓存中存的是空值,也就是数据库中没有对应的数据。这时候返回 null 是因为我们知道数据库里没有这个数据,所以不需要再去数据库查询了。这是一个优化,避免数据库不必要的查询负担。

if(!hasLock){ Thread.sleep(50); return queryWithMutex(id); }这里为什么直接写return queryWithMutex(id);

这段代码是在处理缓存重建时使用互斥锁的逻辑。当一个线程尝试获取互斥锁失败时(即!hasLock为真),它意味着另一个线程已经在重建缓存。在这种情况下,当前线程不应立即放弃,而是应该稍微等待一会儿,然后再次尝试获取数据。

这里的Thread.sleep(50);是为了避免立即重试,从而给正在进行缓存重建的线程一些时间来完成它的操作。休眠结束后,当前线程通过递归调用return queryWithMutex(id);再次尝试访问该方法,这时候它将有机会:

  1. 从缓存中获取到新的数据(如果先前持有互斥锁的线程已经完成了缓存重建)。
  2. 如果缓存依旧没有被重建(可能由于持有锁的线程需要更长的时间来重建缓存),那么它会再次尝试获取互斥锁。

使用递归的好处是可以重用相同的逻辑来再次尝试获取数据,而不需要编写额外的循环逻辑。不过,这样做也要小心栈溢出错误,特别是当重试次数非常多的时候。递归调用次数应该有一定的限制,防止无限递归的发生。在实际的生产环境中,通常会设置一个最大重试次数来避免这种问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值