Redis 缓存击穿问题 解决方案(一) 互斥锁

假设一个热门产品的缓存时间到期了,那么将会有大量的请求查询不到缓存,就只能去查询数据库然后再把数据添加到缓存中。但是如果在缓存时间到期的瞬间有很多个请求都来查询这个热门产品,因为缓存当中查询不到数据,导致他们都无法得到数据,只能够去查询数据库,这样便会造成数据库的压力过大,甚至可能导致宕机

​ 为防止所有请求都直接访问数据库,于是就有了如下两种解决 缓存击穿 问题的方案。

  • 互斥锁

  • 逻辑过期

    本文通过方案一 互斥锁 来解决缓存击穿问题。

    ​ 锁具有 互斥性,加锁之后线程从原来的 并行 变成了 串行。 第一个线程过来访问,获得锁,只有第一个线程能够去直接访问数据库,然后把数据写入缓存。第二个线程过来,没得到锁,只能不断重试去获得锁,直至第一个线程释放锁,然后第二个线程就能够直接从缓存中获得数据。
    互斥锁 流程图如下:
    互斥锁解决缓存击穿

    首先编写一个获得锁的方法。

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

    ​ 接下来编写业务方法,查询一个热门产品。方法如下。

        @Override
        public Result queryById(Long id) {
            //缓存穿透
            //Shop shop = queryWithPassThrough(id);
            //互斥锁解决缓存击穿
            Shop shop = queryWithMutex(id);
            if (shop == null) {
                return Result.fail("店铺不存在!");
            }
            //返回
            return Result.ok(shop);
        }
    

    ​ 缓存带来的问题有很多,有 缓存穿透缓存击穿缓存雪崩 等等。一个查询产品信息的方法可能会同时存在缓存穿透和缓存击穿的问题,如何同时解决这两个问题这里暂不涉及,只关注如何解决缓存击穿问题。

    ​ 在查询产品信息的方法内我们又调用了另外一个方法 queryWithMutex(id) ,代码如下。

    public Shop queryWithMutex(Long id) {
            String key = CACHE_SHOP_KEY + id;
            //1.从redis查询商铺缓存
            String shopJson = stringRedisTemplate.opsForValue().get(key);
            //2.判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //3.存在,直接返回
                Shop shop = JSONUtil.toBean(shopJson, Shop.class);
                return shop;
            }
            if (shopJson != null) {
                return null;
            }
            //4.实现缓存重建
            //4.1获取互斥锁
            String lockKey = "lock:shop:" + id;
            Shop shop = null;
            try {
                boolean isLock = tryLock(lockKey);
                //4.2判断是否获取成功
                if (!isLock) {
                    //4.3失败,则休眠并重试
                    Thread.sleep(50);
                    //递归
                    return queryWithMutex(id);
                }
                //4.4成功,根据id查询数据库
                shop = getById(id);
                //5.不存在,返回错误
                if (shop == null) {
                    //缓存击穿问题
                    //将空值写入redis
                    stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                    return null;
                }
                //6.存在,写入redis
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                //7.释放互斥锁
                unlock(lockKey);
            }
            //8.返回
            return shop;
        }
    

    ​ 至此,缓存击穿 的问题就被解决了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐