业务使用Redis做缓存,当有数据更新时,如何保证缓存及时更新
读数据流程
请求到来,业务代码会先查Redis,查不到再去查DB,并将结果写入Redis
写数据方案
1. 先删除缓存,再更新DB
可行性
先删除缓存,再更新DB,下次读请求到来会从数据库查到新的数据更新到缓存中。如果先更新缓存,在更新DB,更新DB失败会导致数据不一致。
问题
容灾不足
如果删除缓存失败的情况,如果业务继续进行,更新DB,那么在缓存过期之前仍然查到的是旧数据。如果业务返回失败,则对Redis变成了强依赖。
并发不安全
考虑如下场景:
- A请求删除缓存,A请求更新DB
- B请求查询缓存,不存在
- B请求查询DB,查到旧数据(更新未完成),写入缓存
- A请求更新DB完成
这就导致缓存中仍存的旧数据,数据不一致。
2. 先更新DB,再删除缓存
这种策略解决了方法1中的并发问题,但是还是有极小可能存在并发问题,考虑如下情况:
- 请求A查询缓存,缓存刚好失效
- 请求A查询DB,得到一个旧值
- 请求B更新数据库
- 请求B删除缓存
- 请求A将查到的旧值写入缓存
这种情况确实会产生数据不一致,但是考虑到DB的读操作总是比写操作快的多,这种场景基本不可能出现。
如何杜绝并发问题
延迟异步删,保证读操作完成后再删除缓存。
如何容灾
上述方案中如果删除缓存失败了怎么办?
引入消息队列
- 更新DB
- 删除缓存,如果失败将要删除的key发送至消息队列
- 消费消息,获得需要删除的key,删除key缓存直到成功
订阅binlog
上述方法对业务代码的侵入性比较大,为此可以启动一个程序订阅MySQL的binlog用来发现数据更新,流程如下:
- 业务代码更新数据库,MySQL将更新操作写入binlog
- 订阅程序提取中更新的数据以及key,尝试删除key的缓存
- 如果删除缓存失败,将key发送至消息队列
- 消费者程序从消息队列中获取待删除的key,重试删除直到成功。