面试题:缓存与数据库双写不一致解决方案
面试 Redis About 1,815 words前提
保证最终一致性的解决方案是缓存设置过期时间。以下方案讨论的是不依赖于给缓存设置过期时间的情况。
方案一:先更新缓存,再更新数据库
不推荐。
先更新缓存若更新数据库失败,还需再更新缓存。
方案二:先更新数据库,再更新缓存
不推荐。
同时有请求A和请求B进行更新操作,请求A与B在不同线程,可能会出现:
- 请求
A
更新了数据库 - 请求
B
更新了数据库 - 请求
B
更新了缓存 - 请求
A
更新了缓存
这就出现请求A
更新缓存应该比请求B
更新缓存早才对,但是因为网络等原因,B
却比A
更早更新了缓存。这就导致了脏数据,因此不考虑。
方案三:先删除缓存,再更新数据库
有点问题。
有一个请求A
进行更新操作,另一个请求B
进行查询操作,可能会出现:
单个数据库
- 请求
A
进行写操作,删除缓存 - 请求
B
查询发现缓存不存在 - 请求
B
去数据库查询得到旧值 - 请求
B
将旧值写入缓存 - 请求
A
将新值写入数据库
读写分离架构
- 请求
A
进行写操作,删除缓存 - 请求
A
将数据写入数据库了, - 请求
B
查询缓存发现,缓存没有值 - 请求
B
去从库查询,这时,还没有完成主从同步,因此查询到的是旧值 - 请求
B
将旧值写入缓存
数据库完成主从同步,从库变为新值
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
解决方案
延时双删策略
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}
翻译翻译
- 先淘汰缓存
- 再写数据库(这两步和原来一样)
- 休眠
1
秒 - 再次淘汰缓存
休眠时间
自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms
即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
对于MySQL
读写分离架构,只是睡眠时间修改为在主从同步的延时时间基础上,加几百ms
。
方案四:先更新数据库,再删除缓存
极端情况有问题。
有一个请求A
进行更新操作,另一个请求B
进行查询操作,可能会出现:
- 请求
A
查询数据库得到一个旧值 - 请求
B
将新值写入数据库 - 请求
B
删除缓存 - 请求
A
将查到的旧值写入缓存
发生这种情况的概率
步骤2
的写数据库操作比步骤1
的读数据库操作耗时更短,才有可能使得步骤3
先于步骤4
。可是,大家想想,数据库的读操作的速度远快于写操作的,因此步骤2
耗时比步骤1
更短,这一情形很难出现。
解决方案
延时双删策略
public void write(String key,Object data){
db.updateData(data);
redis.delKey(key);
Thread.sleep(1000);
redis.delKey(key);
}
翻译翻译
- 先写数据库
- 再淘汰缓存
- 休眠
1
秒 - 再次淘汰缓存
方案三与方案四还存在问题
问题
- 同步双删导致并发降低
- 比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况。
问题一解决方案
异步。
问题二解决方案
提供一个保障的重试机制。
方案一:消息队列方式
- 更新数据库数据
- 缓存因为种种问题删除失败
- 将需要删除的
key
发送至消息队列 - 自己消费消息,获得需要删除的
key
- 继续重试删除操作,直到成功
业务线代码侵入较大。
方案二:订阅binlong
方式
- 更新数据库数据
- 数据库会将操作信息写入
binlog
日志当中 - 订阅程序提取出所需要的数据以及
key
- 另起一段非业务代码,获得该信息
- 尝试删除缓存操作,发现删除失败
- 将这些信息发送至消息队列
- 重新从消息队列中获得该数据,重试操作。
订阅binlog
程序在MySQL
中有阿里开源的中间件叫canal
。
备注
如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试也可。
总结
根据数据实时性要求,以及系统并发量考虑。
实时性不强,则可以选择设定缓存过期时间,先删缓存再更新数据库或先更新数据库再删缓存方案都可行。
实时性较强的,又有大并发量可以考虑延迟双删策略。
至于其他如请求串行化,放入同一个队列中依次执行的,复杂没必要。
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓