缓存和数据库如何保证一致性
1. 问题提出
正常情况下,我们从客户端
发起请求从数据库
拿到数据
当客户端
请求变多的时候,我们就会引入缓存
来提高接口的性能
但是,由于请求的并发性和数据处理的顺序性,更新数据是很有可能导致缓存和数据库不一致的
那该如何操作才能保证数据库和缓存的一致性呢?
2. 四种情况下的分析
针对缓存和数据库一致性的问题,需要分以下四种情况
1️⃣先更新数据库,再更新缓存
场景
假设,这里有两个请求,请求a更新字段为1,请求b更新字段为2,那么有如下顺序:
请求a先更新数据库为1,然后请求b更新数据库为2
接着请求b更新缓存为2,最后请求a更新缓存为1
最终结果导致数据库为2,缓存为1,造成缓存和数据库不一致的问题
结论
不推荐使用这种方法
2️⃣先更新缓存,再更新数据库
场景
假设,这里有两个请求,请求a更新字段为1,请求b更新字段为2,那么有如下顺序:
请求a更新缓存为1,请求b更新缓存为2
请求b更新数据库为2,最后请求a更新数据库为1
最终结果导致数据库为1,缓存为2,造成缓存和数据不一致
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nojSN7V-1653876873566)(img/WtjS1qaeCzryVHK.png)]
结论
不推荐使用这种方法
Cache Aside策略
后两种要讨论的解决方案是根据**Cache Aside策略(旁路缓存策略)**来进行制定的
旁路缓存策略是指,更新数据时(写策略),不更新缓存,而是删除缓存中的数据,然后到读取数据时(读策略),发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中
但是存在是先删除缓存再更新数据库还是先更新数据库再删除缓存的问题
3️⃣先删除缓存,再更新数据库
场景
假设,这里有两个请求,请求a需要更新字段为21,请求b需要读取字段,那么有如下顺序:
请求a先删除缓存,导致请求b的未命中缓存**(但是请求a还没完成对数据库数据的更新)**
接着,请求b读取数据库的值,此时数据库为20,然后更新到缓存当中,此时缓存为20
最后,请求a姗姗来迟更新数据库为21
最终,导致缓存为20,数据库为21,造成缓存和数据不一致
解决方法——采用延时双删策略
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
伪代码如下
public void write(String key,Object data){
db.updateData(data);
redis.delKey(key);
Thread.sleep(1000);
redis.delKey(key);
}
解释:
(1)先更新数据库
(2)淘汰缓存
(3)休眠1秒,再次淘汰缓存好处:这么做,可以将1秒内所造成的缓存脏数据,再次删除。
结论
不推荐使用这种方法!!
延迟双删一般是针对第三种情况,因为 “删除了缓存–>再更新完数据库” 期间的数据库更新比较慢,大概率有其他请求拿到旧数据放到缓存,所以可以更新完数据库可以休眠一下,然后再删除缓存,即延迟双删。不过这个休眠时间不太好确定,所以一般不推荐
4️⃣先更新数据库,再删除缓存(推荐)
场景
假设这里请求a需要读取字段,请求b需要更新字段为21,有如下顺序
- 首先请求a要读取数据,刚好未命中缓存,就接着去读取数据库,值为20**(此时请求a还没完成缓存更新工作)**
- 此时,请求b进来了,更新数据库为21,然后删除了缓存
- 最后,请求a珊珊来迟,按照请求a的逻辑是要将20写入到缓存
最终,导致缓存为20,数据库数据是21,造成缓存和数据不一致
解决方法
但是上述这种情况是不太可能发生的,因为缓存的写入通常要远远快于数据库的写入
而一旦请求A早于请求B删除缓存之前更新了缓存,那么接下里的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现数据库和缓存不一致的情况
另外,因为更新数据库和删除缓存不是原子性操作(指的是更新数据库和删除缓存一起的业务逻辑) ,如果删除缓存操作没完成,还是会出现不一致的情况,所以可以兜底给数据加入过期时间,那么不一致时间最多只到过期,可以达到最终一致性
结论
推荐使用这种方案
资料来源