文章目录
- 1. 缓存的更新机制
- 1.1 被动更新
- 1.2 主动更新
- 1.2.1 Cache Aside Pattern (更新数据库,再删除缓存)
- 1.2.2 更新数据库,更新缓存
- 1.2.3 先删除缓存,在更新数据库
- 1.3 Read/Write Through Pattern
- 1.4 Write Behind Caching Pattern
- 2. 缓存的清理机制
- 2.1 时效性清理
- 2.2 数目阀值清理机制
- 2.3 软应用清理
- 2.4 redis 的8中内存淘汰策略
- 2.5 缓存清理机制的总结
- 3. 缓存问题
- 3.1 缓存穿透
1. 缓存的更新机制
1.1 被动更新
为缓存设定过期时间,失效从数据库读取,再次写入缓存
调用方 暂存方(缓存) 数据提供方
被动:有效期到后,再次写入。
-
客户端 查数据,缓存中没有,从提供方获取,写入缓存(有一个过期时间t)。
-
在t内,所有的查询,都由缓存提供。所有的写,直接写数据库。
-
当 缓存数据 t 到点了,缓存 数据 变没有。后面的查询,回到了第1步。
适合:对数据准确性和实时性要求不高的场景。比如:商品 关注的人数。
1.2 主动更新
1.2.1 Cache Aside Pattern (更新数据库,再删除缓存)
这是最常用的更新机制
- 失效: 应用程序从cache中获取数据,没获取到,则从数据库中读取数据,成功后,放到缓存中
- 命中: 应用程序从缓存中获取数据,得到后直接返回
- 更新: 先把数据库中的数据更新,成功后,在将缓存删除
有可能产生的问题
比方一个读操作,一个写操作的并发,读操作没有了删除缓存的操作,直接命中拿的是缓存中的数据,写操作更新了数据库中的数据,并删除了缓存,读操作读的就是老的数据
但是这种情况只是理论上存在,实际上很少出现,因为这种情况的产生是需要读操作慢于写操作,一般情况下,写操作都是比读操作慢,并且要加事务锁表,所以很少出现这种情况
1.2.2 更新数据库,更新缓存
一般也不采用。
请求被阻塞,
业务要求:修改了数据库,缓存的值需要通过大量时间的计算才能进行更新,影响了响应时间,直接删了缓存,比较节省计算时间,当用户再次去查询的时候,发现缓存的值不存在,用户只要经过一次复杂的计算就能对缓存的值进行更新
1.2.3 先删除缓存,在更新数据库
一般不采用,因为大概率 读比写快。
一个读请求和一个写请求的并发,当写请求进来,把缓存删了,更新数据库这步操作还没完成,读请求进来,发现没缓存,往数据库中读取,这个时候数据库的数据还是老的数据,读请求把老的数据写入了缓存,然后写请求才把数据更新到数据库,这就造成了数据库和缓存的双写一致性问题
解决双写一致性问题:延迟双删
延迟双删
就是在更新晚数据库之后,sleep一段时间,再次进行删除缓存的操作,能极大的保证双写一致性
昨天被高德一个面试问:说,你这个延时双删有这么几步操作。如果其中某一步失败了这么办?
删除缓存
更新数据库:事务,回滚就OK。
第二次删除缓存
重试删除:当你前面的操作,无法回滚时,为了保证后续数据的一致性,
(最便宜的做法)硬着头皮往前走,重试。
借用中间件:消息队列,重发消息。
系统外订阅:canal。binlog。
二次删除key,和我们的业务代码解耦。
1.3 Read/Write Through Pattern
这个是直接更缓存打交道,所有的增删改查都在缓存上进行,不会出现数据一致性的问题,但是缓存挂了的话,数据容易丢失
1.4 Write Behind Caching Pattern
更新数据的操作直接在缓存中进行,然后再异步对数据库进行更新,带来的好处就是数据的IO操作非常的快。带来的问题就是可能产生数据的丢失
2. 缓存的清理机制
2.1 时效性清理
就是给缓存设置一个过期时间,到期自动清理
2.2 数目阀值清理机制
判断缓存中的缓存的数量 达到一定值 ,对缓存进行清理。
阈值:根据自己的业务来定。1g,1m,1024个, 800 80%。
2.3 软应用清理
当空间不足的时候,会被回收
2.4 redis 的8中内存淘汰策略
a) 针对设置了过期时间的key做处理:
-
volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
-
volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
-
volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
-
volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除
b) 针对所有的key做处理 :
-
allkeys-random:从所有键值对中随机选择并删除数据。
-
allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
-
allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
c) 不处理:
-
noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
LRU 算法(Least Recently Used,最近最少使用)
淘汰很久没被访问过的数据,以最近一次访问时间作为参考。(淘汰这段时间中最旧的key)
LFU 算法(Least Frequently Used,最不经常使用)
淘汰最近一段时间被访问次数最少的数据,以次数作为参考。(淘汰这段时间使用次数最少的key)
2.5 缓存清理机制的总结
时效性清理+数目阀值:
防止:短期内,密集查询,导致缓存空间的急剧增大lru+软引用:
保证热数据,最大限度的提高缓存命中率
3. 缓存问题
3.1 缓存穿透
缓存中没有值,数据库中也没有这个值
产生可能原因
1、自身业务代码或者数据出现问题;
2、一些恶意攻击、 爬虫等造成大量空命中。
问题解决
1. 缓存空对象
注意:对于不存在的空对象,一定要设置过期时间!
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;
}
}
2. 布隆过滤器
布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。