Redis雪崩
-
雪崩概念
当redis中大量缓存的过期时间相同时,缓存到达过期时间集体失效(雪崩效应),大量请求绕过缓存层直接访问数据库load数据,导致数据库频繁IO,性能下降乃至宕机崩溃。
-
图解雪崩过程
一个简单代码层图示雪崩后未处理的后果
-
常用解决方案
-
分散过期时间
随机
redis
缓存的过期时间,使得缓存失效时间点分散开来。 -
mq削峰策略
依靠
mq
削峰特性,发生雪崩效应时load数据过程走mq同步消息方式,mq消息放入顺序队列中,消费者消费第一个消息从数据库load数据并重新加入到缓存中,后续的相同消息则直接从缓存返回无需访问数据库。 -
锁方式
/** * 获取字典信息 雪崩后加锁 * * @param key * @return */ public String getDictInfoCrashByLock(String key) { //从redis中获取值 String dictValue = getFromRedis(key); if (dictValue == null) { //这里以本地可重入锁方式解决单机 // 如果分布式环境就得换分布式锁比如redis分布式锁或zookeeper分布式锁 Lock lock = new ReentrantLock(); lock.lock(); try { //当redis中不存在时访问数据库 dictValue = sysDictDbService.loadData(key); //从数据库中加载数据并放入redis if (dictValue != null) { setValueToRedis(key, dictValue); } } finally { lock.unlock(); } } return dictValue; }
锁的形式可以保证重新载入数据的执行只有一个线程执行。这样就避免了大流量的访问数据库。
-
限流法
限流方式也类似锁的形式,为了控制雪崩效应后请求数据库的流量。
具体限流算法和实现可以参考文章:
-
Redis缓存穿透
-
穿透概念
缓存穿透是指请求查询的数据在缓存中不存在且在数据库中也不存在,比如
redis
和数据库中都只有id为1、2、3
的数据,而请求查询id为4、5、6
等不存在的数据,这类请求是每次都会穿透redis
缓存访问数据库,如果程序未加以防范则可能会被恶意攻击。 -
图解穿透过程
-
常用解决方案
-
规则排除
如上面id查询,我们可以定义一个符合id规则的过滤器,如果不符合规则的请求(如
xxx
)则直接返回空。 -
null
值填充当缓存穿透时,
redis
存入一个类似null
的值,下次访问则直接缓存返回空,当数据库中存在该数据的值则需要把redis
存在的null
值清除并载入新值,此方案不能解决频繁随机不规则的key请求。/** * 获取字典信息-穿透redis存<null> * * @param key * @return */ public String getDictInfoStrikeByNull(String key) { String nullTag = "<null>"; //1、从redis中获取值 String dictValue = getFromRedis(key); if (dictValue == null) { //当redis中不存在时访问数据库 dictValue = sysDictDbService.loadData(key); //从数据库中查询不到数据则redis填充<null> String toRedisValue = Optional.ofNullable(dictValue).orElse(nullTag); setValueToRedis(key, toRedisValue); } //此方法需依赖数据库插入新数据是需要清除redis已经存在的<null>标记值 //如果从redis取得是null标记字符则也返回null return nullTag.equals(dictValue) ? null : dictValue; }
-
一级二级缓存法/布隆过滤器('糊涂’工具小试)
此方法解决方案就是类型一个
bitmap
存放对应数据的标记为,1表示有数据0表示无数据,,一般是查询id的hashkey做hash取模算法快速定位所在bitmap
位置的标记位,当数据库插入数据时往对应位置标记为1表示有数据,下次走redis
缓存前先访问这个bitmap
,如果不存在则返回空。以下使用github上开源工具类hutool的布隆过滤器演示。maven依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.2.1</version> </dependency>
简化实现代码
//hutool bitmap布隆过滤器 private BloomFilter bloomFilter = new BitMapBloomFilter(10000); /** * 是否有效key * * @param key * @return */ private boolean bloomContains(String key) { return bloomFilter.contains(key); } /** * 添加到布隆过滤器中,一般数据库插入时候添加 * * @param key */ private void addKeyToBloomFilter(String key) { bloomFilter.add(key); } /** * 获取字典信息-穿透布隆过滤器 * * @param key * @return */ public String getDictInfoByBloomFilter(String key) { //布隆过滤器检查无效key直接返回空 if (!bloomContains(key)) { return null; } //1、从redis中获取值 String dictValue = getFromRedis(key); if (dictValue == null) { //当redis中不存在时访问数据库 dictValue = sysDictDbService.loadData(key); if (dictValue != null) { //从数据库中加载数据并放入redis setValueToRedis(key, dictValue); //添加到bitmap中 addKeyToBloomFilter(key); } } return dictValue; }
-
Redis缓存击穿
-
击穿概念
缓存击穿是key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,频繁访问数据库,此和缓存雪崩有点类似,区别就是雪崩是不同的key集体同时过期,击穿则是单个key过期大量请求,解决方案和雪崩类似就不多阐述了。