redis缓存穿透,缓存击穿,缓存雪崩实战解决方案·

本文介绍了缓存系统中的三种常见问题:缓存穿透、缓存击穿和缓存雪崩,以及相应的解决方案,如使用布隆过滤器拦截非法请求、互斥锁保证强一致性与逻辑过期处理过期数据。
摘要由CSDN通过智能技术生成

1.缓存穿透

原因:恶意攻击一个缓存和数据库都不存在的key,使数据库处理大量请求,增大数据库压力。

解决方案:

  1. 缓存null值:当查询到数据库为空时,在缓存中写入一个null值,使后续请求命中缓存。缺陷:缓存压力增大,一旦数据库中真的存在了这个key,则出现缓存与数据库不一致问题。
  2. 布隆过滤器:在缓存预热时设置布隆过滤器,请求先到达布隆过滤器,非法请求将直接拦截。

注意:在新增和删除数据时,布隆过滤器也要随之进行更新!

缓存预热:

@Component
public class RedisHandler implements InitializingBean {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IShopService shopService;
    // 预期插入数量
    static long expectedInsertions = 200L;
    // 误判率
    static double falseProbability = 0.01;
    private RBloomFilter<Long> bloomFilter;

    @Resource
    private BloomFilterUtil bloomFilterUtil;

    @Override
    public void afterPropertiesSet() throws Exception {
        //缓存预热
        //1.查询热点数据
        List<Shop> list = shopService.list();
        //2.设置布隆过滤器
        bloomFilter = bloomFilterUtil.create("shopBloomFilter", expectedInsertions, falseProbability);
        //3.添加缓存
        for (Shop shop : list) {
            redisTemplate.opsForValue().set("shop", JSONUtil.toJsonStr(shop));
            bloomFilter.add(shop.getId());
        }
    }
}
public Result queryByIdBloom(Long id){
    //1.通过布隆过滤器
    if (!bloomFilter.contains(id)){
        //2.非法查询,返回空
        return Result.fail("shop not exist!");
    }
    //TODO 后续业务逻辑
    return null;
}

布隆过滤器实现原理:
在这里插入图片描述
bitmap:是一个以bit为单位的数组,数组中每个单元只能存储二进制数0或1。

布隆过滤器作用:可以检索一个元素是否存在集合中。

在这里插入图片描述

误判情况:
在这里插入图片描述

误判率:数组越大误判率越小,数组越小误判率越大。误判率是肯定会存在的,我们一般可以设置误判率,不要超过5%。项目上也可以接受。

2.缓存击穿

原因:当给某一个key设置了过期时间,当key过期的时候恰好又有大量请求并发进来直接命中数据库,可能会直接把数据库压垮。

解决方案:

  1. 互斥锁:当线程1查询缓存未命中时,获取互斥锁,查询数据库重建缓存。此时其他线程进来获取锁失败,不断重试直到命中缓存。(强一致性,性能稍差一些)

    @Retryable(value = {RedisNxException.class}, maxAttempts = 3)
    public Result queryByIdNx(Long id) throws Exception {
        //1.先查找缓存
        String cacheShop = redisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        if (cacheShop == null) {
            //2.缓存未命中,获取互斥锁重建缓存
            if (getLock("shopnx" + id)) {
                //3.获取锁成功,重建缓存
                Shop shop = shopMapper.selectById(id);
                redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
                //4.释放锁
                deleteLock("shopnx" + id);
                //5.返回
                return Result.ok(shop);
            } else {
                //获取锁失败,等待重试
                Thread.sleep(500);
                //当方法抛出异常时,会重试
                throw new RedisNxException("获取锁失败,等待重试");
            }
        }
        //缓存命中直接返回
        return Result.ok(cacheShop);
    }
    
  2. 逻辑过期:在设置缓存时设置逻辑过期时间,当线程1查询缓存发现逻辑已过期时,获取锁,开启新线程重建缓存,之后返回过期数据。其他线程获取锁失败后直接返回过期数据。(性能好,数据稍有不一致)

    public Result queryByIdExpire(Long id) {
        //1.先查找缓存
        String cacheShop = redisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        RedisData redisData = JSONUtil.toBean(cacheShop, RedisData.class);
        if (redisData.getExpireTime().isBefore(LocalDateTime.now())) {
            //2.发现缓存逻辑已过期,获取互斥锁,开启新线程重建缓存
            if (getLock("shopnx" + id)) {
                //3.获取锁成功,开启新线程重建缓存
                CACHE_REBUILD_EXECUTOR.submit(() -> {
                    try {
                        //4.重建缓存
                        Shop shop = shopMapper.selectById(id);
                        String jsonStr = JSONUtil.toJsonStr(new RedisData(LocalDateTime.now().plusDays(1L),                                         shop));
                        redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr);
                    } catch (Exception e) {
                        ShopServiceImpl.log.info(e.getMessage());
                    } finally {
                        //5.缓存重建完毕,释放锁
                        deleteLock("shopnx" + id);
                    }
                });
                //返回旧数据
                return Result.ok(redisData.getObject());
            } else {
                //获取锁失败,返回旧数据
                return Result.ok(redisData.getObject());
            }
        }
        //未过期直接返回
        return Result.ok(redisData.getObject());
    }
    

3.缓存雪崩

原因:大批热点数据同时失效,导致大量请求同一时间直达数据库,造成数据库崩溃。

解决方案:

  1. 给相同过期时间的key添加随机TTL。
  2. 给缓存业务添加限流降级策略。
  3. 给业务添加多级缓存。(Guava)
  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

紫为你棋菲

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

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

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

打赏作者

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

抵扣说明:

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

余额充值