Redis 缓存的数据一致性问题
系统加入缓存后,在一些极端情况下,就会出现缓存和数据库的数据不一致,导致读取到脏数据
首先需要确定一点的是不可能做到绝对的一致性,原因是 redis 和数据库本身就是两个系统,他们之间没法实现事务,无法保证原子性,在高并发的情况下,不管是先更新数据库,还是缓存,总会有一个时刻 redis 和 数据库的数据时不一致的,如果业务要求高一致性就不要上 redis
更新策略
当数据发生变化时,对数据库和缓存的操作如下
更新操作
对数据库的更新操作:
- 直接更新数据
对缓存的操作:
-
删除缓存,等下次访问数据时再从数据库中获取数据,添加到缓存
-
直接更新缓存
更新顺序
缓存和数据库更新顺序:
-
先数据库后缓存
-
先缓存后数据库
所以总共有 4 种更新策略
四种策略
先更新数据库,后删除缓存
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | 缓存失效 | v1 | null | |
2 | 读取数据库为v1 | v1 | null | |
3 | 更新数据库为v2 | v2 | null | |
4 | 删除缓存 | v2 | null | |
5 | 写入缓存v1 | v2 | v1 |
先更新数据库,后更新缓存
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | v0 | v0 | ||
2 | 更新数据库为v1 | v1 | v0 | |
3 | 更新数据库为v2 | v2 | v0 | |
4 | 更新缓存为v2 | v2 | v2 | |
5 | 更新缓存为v1 | v2 | v1 |
先删除缓存,后更新数据库
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | 删除缓存 | v1 | null | |
2 | 缓存失效 | v1 | null | |
3 | 读取数据库 | v1 | null | |
4 | 写入缓存v1 | v1 | v1 | |
5 | 更新数据库为v2 | v2 | v1 |
先更新缓存,后更新数据库
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | v0 | v0 | ||
2 | 更新缓存为v1 | v0 | v1 | |
3 | 更新缓存为v2 | v0 | v2 | |
4 | 更新数据库为v2 | v2 | v2 | |
5 | 更新数据库为v1 | v1 | v2 |
如何选择
排除更新缓存策略
首先排除先更新缓存,后更新数据库;
-
我们可以发现前三种最终都是缓存为旧值,数据库为新值;
而对于最后一种策略而言,它最终缓存为新值,数据库为旧值,而缓存中的数据没有持久化,是易丢失的,这种情况下十分危险,所以也是基本不推荐的
再者排除先更新数据库,后更新缓存,原因主要是从浪费性能方面考虑:
- 如果是需要写入操作多,读取操作少的业务,那么,会出现数据还没被读取过就又被更新了,浪费性能;
- 写入缓存的值可能是经过计算后写入的,那么频繁修改缓存也是一种性能浪费;
“先数据库,后缓存” 和 “先缓存,后数据库”
我们需要考虑两种策略出现数据不一致的可能性
实现我们要明确在 缓存删除和写入的速度是 要小于 数据库查询和更新的速度,所以主要的耗时较大的是在数据库查询和更新上;
而数据库的查询一般是快于数据库更新的
先缓存,后数据库
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | 删除缓存 | v1 | null | |
2 | 缓存失效 | v1 | null | |
3 | 读取数据库 | v1 | null | |
4 | 写入缓存v1 | v1 | v1 | |
5 | 更新数据库为v2 | v2 | v1 |
使用该策略时,数据不一致主要是由 线程 A 完成更新数据库 晚于 线程 B 读取数据库写入缓存 导致的,而数据库查询就是快于数据库更新的,所以这种情况发生的可能性是比较大的
先数据库,后缓存
时间 | 线程A | 线程B | 数据库 | 缓存 |
---|---|---|---|---|
1 | 缓存失效 | v1 | null | |
2 | 读取数据库为v1 | v1 | null | |
3 | 更新数据库为v2 | v2 | null | |
4 | 删除缓存 | v2 | null | |
5 | 写入缓存v1 | v2 | v1 |
使用该策略时,导致数据不一致的原因是 线程 A 的写入缓存v1 晚于 线程 B 的删除缓存,而读取数据库并写入缓存 一般是要比 更新数据库删除缓存快的,步骤5要晚于步骤4执行的可能性较低
总结
-
无论哪一种策略都会引起数据不一致,验证了我们一开始说的结论
-
先更新缓存,后更新数据库不可取
-
更新缓存操作性能浪费,不推荐
-
选择先更新数据库后删除缓存策略,出现数据不一致的可能性低
其他引起数据不一致的情况
以上的讨论都是建立在每项操作都可以执行成功的前提下,但是如果出现删除或更新缓存失败时,我们不采取异常处理的话也是会出现数据不一致的;
解决方法(待完善)
可以启动一个线程重复执行删除缓存操作直到成功,具体似乎是使用消息队列(本人还没学习到,此处先不写了)
参考文章
http://www.kokojia.com/article/45871.html
https://blog.csdn.net/hukaijun/article/details/81010475