深入理解redis——缓存雪崩/缓存击穿/缓存穿透

苏凌峰

1.缓存雪崩

2.缓存击穿

3.缓存穿透

4.总结

1.缓存雪崩
缓存雪崩是如何发生的?
1)redis服务直接挂掉,redis全盘崩溃
2)redis中有大量缓存同时过期,导致大量查询直击mysql

解决
1.1)redis缓存集群实现高可用,主从+哨兵
1.2)ehcache本地缓存 + Hystrix或者阿里sentinel限流&降级
1.3)开启Redis持久化机制aof/rdb,尽快恢复缓存集群

2.1)设置缓存更新的时间都为固定时间+随机一定的时间,造成不会同时过期

2.缓存击穿
缓存雪崩是如何发生的?
大量的请求正在访问同一个key, 此时这个key正好失效了,就会导致大量的请求到数据库上面。

危害:
会造成某一时刻的mysql压力过大,有宕机风险。

解决方案:
1)对于高热点key,设置永不过期
2)设置一个互斥锁,来防止缓存击穿:

    public TUser findById(Integer id) {
        TUser user  = (TUser)redisTemplate.opsForValue().get(CACHE_KEY_USER + id);
        if(user==null){
            //小厂用
          /*  user = userMapper.selectById(id);
            if(user!=null) {
                redisTemplate.opsForValue().set(CACHE_KEY_USER + id, user);
            }*/
            //大厂用,对于高qps的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
            synchronized (TUserServiceImpl.class){
                //双端检索,再次查询redis
                user  = (TUser)redisTemplate.opsForValue().get(CACHE_KEY_USER + id);
                if(user==null){
                    //还是为空,就去查mysql
                    user = userMapper.selectById(id);
                    if(user!=null){
                        redisTemplate.opsForValue().setIfAbsent(CACHE_KEY_USER + id, user,7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }

3)双缓存保存,定时轮询,互斥更新,差异失效时间

需求:假设我们要完成网页上的一个聚划算功能,每隔两个小时换一批商品。比如:8:000~10:00一批商品,10:00~12:00一批商品。如果按照定时任务的做法,8:00开始更新商品,假设数据量很大,mysql在很难查出来的情况下,到点了可能缓存已经过期了,但是mysql还没查询出商品,又会有大量查询攻击mysql,导致服务宕机。

思路:我们设置两个缓存,都保存相同的内容,两个缓存的过期时间不同。缓存B比缓存A过期时间更长一些,这样就可以保证缓存里永远有数据。

查询:先查询缓存A,如果A没有,查询缓存B。

//采用redis list数据结构的lrange命令实现分页查询
list = this.redisTemplate.opsForList().range(Constants.JHS_KEY_A, start, end);
if (CollectionUtils.isEmpty(list)) {
log.info("=========A缓存已经失效了,记得人工修补,B缓存自动延续5天");
//用户先查询缓存A(上面的代码),如果缓存A查询不到(例如,更新缓存的时候删除了),再查询缓存B
this.redisTemplate.opsForList().range(Constants.JHS_KEY_B, start, end);

更新:先更新缓存B,再更新缓存A。

//先更新B缓存
this.redisTemplate.delete(Constants.JHS_KEY_B);
this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY_B,list);
this.redisTemplate.expire(Constants.JHS_KEY_B,20L,TimeUnit.DAYS);
//再更新A缓存
this.redisTemplate.delete(Constants.JHS_KEY_A);
this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY_A,list);
this.redisTemplate.expire(Constants.JHS_KEY_A,15L,TimeUnit.DAYS);

注意:采用互斥更新和查询,保证两个key都有值。

3.缓存穿透
缓存穿透是什么:
请求查询一条记录,先去redis查询后查询不到数据再去mysql查询后也查询不到数据,所以每次请求都会攻击mysql,导致数据库压力暴增

危害:第一次来查询后,一般我们有回写redis机制,所以偶发一次没关系,但是频繁发生就有安全隐患。

解决方案:
1)空对象或者缺省值
一般来说这么做是没问题的,但是如果黑客大量拿未知的key来攻击我们的系统,每次都要去查询,且redis中无用key越写越多。

2)Redis布隆过滤器解决缓存穿透
深入理解redis——布隆过滤器BloomFilter
可以使用布隆过滤器来解决缓存穿透,布隆过滤器已经在这篇文章里讲解过了。

4.总结
今天学习了缓存雪崩/缓存击穿/缓存穿透的现象以及对应方案。

阅读 623

你的迷惑在于想得太多而书读的太少。

16 声望
14 粉丝
0 条评论

你的迷惑在于想得太多而书读的太少。

16 声望
14 粉丝
文章目录
宣传栏