多级缓存架构
Redis常见问题
缓存穿透
说明
缓存穿透是指查询一个根本不存在的数据,缓存和数据库都不会命中,如果数据库查不到则不写入缓存.这样将导致不存在的数据的每次请求都要查询数据库,失去了缓存保护后端数据库的意义.
造成原因
1.自身业务代码或数据出现问题.
2.一些恶意攻击、爬虫等造成大量空命中.
解决方案
- 缓存空对象
// 去数据库获取数据等时候,如果为null,就塞一个空对象到缓存,防止连续访问,还得设置时间,如果这个key有了真实的值后再去获取
String get(String key) {
// 从缓存中获取数据
String cacheValue = cache.get(key);
// 缓存为空
if (StringUtils.isBlank(cacheValue)) {
// 从存储中获取
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存储数据为空, 需要设置一个过期时间(300秒)
if (storageValue == null) {
cache.expire(key, 60 * 5);
}
return storageValue;
} else {
// 缓存非空
return cacheValue;
}
}
- 布隆过滤器
对于恶意攻击,向服务器请求大量不存在的数据导致缓存击穿,还可以使用布隆过滤器先做一次过滤,不让请求再往后端发送.核心:当布隆过滤器说某个值存在时,不一定存在;当它说这个值不存在时一定不存在.
// 布隆过滤器
1.首先会将缓存中的key通过一系列的hash算法,找到布隆过滤器对应数组的索引,标记'1'.
2.get()数据时,也会将key进行一系列的hash算法,如果算出来的值,都是'1',说明这个值可能存在.如果有不为'1'的那就一定不存在.
3.不同担心hash冲突,布隆过滤器的数组会非常大,通常是百亿
4.相较于第一种往缓存中放空对象的方式,布隆过滤器更加的节省空间.
总结
缓存击穿
说明
指缓存中没有数据,数据库中有数据。一般是并发用户多,缓存时间刚好到期,缓存中读不到数据,直接去数据库读取,造成数据库压力大。
解决方案
- 设置热点数据永远不过期。
- 缓存加锁重建
代码里加互斥锁,缓存中没数据,第一个进入的线程获取锁从数据库读取数据,读到后再放入缓存,其他线程等待,再重新去缓存中获取。
// 伪代码 -- 热点缓存key重建优化
String get(String key) {
// 从缓存中获取数据
String cacheValue = cache.get(key);
// 缓存为空
if (StringUtils.isBlank(cacheValue)) {
if(lock){
// 从存储中获取
String storageValue = storage.get(key);
cache.set(key, storageValue);
}
// 释放锁
unlock();
}else{
// 睡眠1秒,再去缓存获取
Thread.sleep(1000);
get(key);
}
...
缓存雪崩
说明
指缓存中大批数据过期,而查询数量巨大,导致数据库压力大,甚至宕机。和缓存击穿不同的是,缓存击穿是并发查同一数据。缓存雪崩时不同数据都过期。
解决方案
- 设置热点数据永不过期。
- 缓存数据的过期时间随机,防止同一时间出现大量数据过期。
缓存数据库读写不一致问题
说明
在大并发下,同时操作数据库和缓存会存在数据不一致性.
造成原因
- 双写不一致
- 读写不一致
解决方案
- 如果业务上能容忍,就加上缓存过期时间,可以解决大部分业务对于缓存的需求。
- 如果无法容忍数据不一致,可以加读写锁保证并发读写或写写的时候按顺序排好队,读读相当无锁。
- 使用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入中间件增加系统复杂性.
Redis淘汰策略
- 被动删除
当读写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key. - 主动删除
由于惰性删除策略无法保证冷数据被及时删除.所以redis会定期主动淘汰一批已经过期的key. - 内存超过maxmemory限定时,触发清理策略
// 4.0之前一共6种,4.0之后一共8种.
// 针对设置了过期时间的key做处理
1.volatile-ttl
在筛选时,会针对设置了过期时间的健值对,根据过期时间的先后进行删除,越早过期的越先被删除.
2.volatile-random
针对设置了过期时间的健值对随机删除.
3.volatile-lru
默认机制,针对设置了时间的健值对,会优先删除最近最少使用的key.(以最近时间为参考)
4.volatile-lfu
针对设置了时间的健值对,会优先删除最近访问次数最少的key.(以次数为参考)
// 针对所有的key做处理
5.allkeys-random
从所有键值对中随机选择并删除数据
6.allkeys-lru
所有健值对中,会优先删除最近最少使用的key.(以最近时间为参考)
7.allkeys-lfu
所有健值对中,会优先删除最近访问次数最少的key.(以次数为参考)
// 不处理
8.noeviction
不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息,这时只能读数据,不能写.