参考:
分布式之数据库和缓存双写一致性方案解析
1. 先更新数据库,再更新缓存
- 不可行。
- 原因分析:
- 频繁更新缓存浪费资源(写多读少的场景)
- 缓存数据计算复杂,浪费性能(可能涉及多张表的计算)
- 线程并发安全问题。(更新缓存的顺序不一致,导致脏数据)
2.先更新数据库,再删除缓存
旁路缓存策略(Cache Aside Pattern)
- 使用方式
- 基本读取: 先读缓存,缓存中没有数据的话,去数据库中读取,然后存入缓存中,同时返回响应。
- 先更新数据库,后删除缓存。
- 存在问题:
- 数据库更新成功,缓存删除失败。导致数据库中的数据是最新的,但缓存中的是旧数据。
- 并发问题
- 读请求去查询缓存时,缓存刚好失效。
- 读请求去查询数据库,得到旧值。
- 写请求将新值写入数据库,写请求删除缓存。
- 读请求将旧值写入缓存。
- 分析并发问题出现的概率
- 概率非常低,因为条件需要具备读缓存失效,而且并发一个写操作。读操作必须在写操作前进入数据库操作,而又要晚于写操作更新缓存。但实际上写操作比读操作慢得多,所以概率非常小。
- 改进:
- 提供一个保障的重试机制即可。目前有两种方案。
- 方案一:可以使用消息队列来保证缓存一致性。
- 流程:
- 更新数据库
- 缓存删除失败
- 将需要删除的key发送至消息队列
- 自己消费消息,获得需要删除的key。
- 继续重试删除操作,直到成功。
- 但这种方式借助了消息中间件,增加了复杂度,对业务代码造成大量侵入。对于一些一致性要求没那么高的场景,就显得没必要了。
- 方案二:启动一个订阅程序去订阅数据库的
binlog
,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
3.先删缓存,再更新数据库
先删除缓存,后更新数据库。
- 假设缓存删除成功,但数据库更新失败。此时新的请求过来,就去数据库中读取旧数据更新到缓存中。
- 并发度很低时适用。
缓存延时双删策略
- 先删除缓存,后更新数据库方案存在的问题分析:
- 同时进入两个请求,一个写请求,一个读请求。
- 写请求先删除
redis
中的数据,然后去数据库进行更新操作。 - 读请求判断
redis
中有没有数据,没有数据时去请求数据库,拿到数据后写入缓存。(此时写请求还未更新DB成功,故读请求拿到的是旧数据) - 写请求更新DB成功后,出现缓存与数据库不一致问题。
- 使用了MySQL的读写分离架构,造成双写不一致的原因:
- 同时进入两个请求,一个写请求,一个读请求。
- 写请求删除缓存,更新成功主库的数据。但还没同步到从库。
- 读请求判断
redis
中有没有数据,没有数据时去请求从库,拿到数据后写入缓存。(此时拿到的是旧值) - 数据库完成主从同步,从库变为新值。
- 解决办法:延时双删策略
- 先删除缓存
- 再写数据库
- 异步等待一段时间后,再次淘汰缓存。(这里的时间设定主要是保证读请求结束,写请求可以删除读请求遭成的缓存脏数据,需要自行评估确定。)
- 该方案解决了高并发情况下,同时有读请求与写请求时导致的不一致问题。读取速度快,但是可能会出现短时间的脏数据。
- 如果第二次删除也删除失败,则需要添加重试机制保证一定删除成功。