回答
一般来说,Redis 和数据库的双写方案有如下四种:
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
由于更新缓存比删除缓存更加复杂且容易产生数据不一致的情况,所以推荐采用删除缓存策略。针对删除缓存的方案,我们可以基于如下策略做选择:
- 业务量不大,并发不高,推荐选择先更新缓存再删除缓存,因为这种方案相对会简单些,所面对的场景也简单些,并发量不高,加上删除缓存失败原本就是一个小概率事件,所以对删除缓存失败的情况的容错性就更大。
- 如果是在高并发的场景下,推荐采用先删除缓存,然后再更新数据库,原因有两个:
- 先更新数据库,再删除缓存:中间存在数据不一致的窗口期,高并发场景下,会放大这个缺陷。
- 针对“读写并发”情况导致数据的不一致性:首先这种情况是一个非常小概率事件,因为读操作是一个非常快的情况,而在这个期间恰好遇到一个写操作耗时的场景,这种概率就会更小了。而且我们可以使用“延迟双删策略”、“分布式锁“等策略让这种方案近乎完美。
三种方案保证数据库与缓存的一致性
延迟双删策略
- 第一次删除缓存:在更新数据库之前先删除缓存中的旧数据。
- 更新数据库
- 延迟一定时间:等待一段时间,确保所有的旧数据读请求都已经穿透到数据库中。
- 第二次删除缓存:再次删除缓存,保所有在数据库更新期间由于缓存失效而从数据库中读取并重新写入缓存的旧数据被清除。
删除缓存重试机制
将第二次删除缓存的数据存储到消息队列中(如 Kafka),由消费者来执行删除操作。
- 如果应用程序第二次删除缓存失败,则从消息队列中重新读取数据,再次删除缓存,这就是重试机制。但是我们需要限定一个重试的次数,如果超过这个限定次数,我们可以报个警。
- 如果缓存删除成功,就把数据从消息队列中删除,避免重复操作。
订阅 binlog 异步删除缓存
数据库更新记录成功后会产生一条变更日志记录在 binlog
,我们可以订阅 binlog
日志,拿到具体的操作数据,然后再执行删除缓存。
扩展
缓存的三种经典更新策略
一般我们有三种使用缓存的经典模式,每种模式对应不同的数据同步策略:
Cache-Aside Pattern
:旁路缓存模式。它要求应用程序在读取数据时,首先要查询缓存,如果缓存中没有所需数据(缓存未命中),则从数据库中加载数据,然后将数据添加到缓存中。Read-Through/Write-through
:这种模式与Cache-Aside Pattern
差不多,只不过在这种模式中,应用程序与缓存之间多了一个中间层Cache-Provider
,这样应用程序就不直接与缓存和数据库交互了,由Cache-Provider
完成交互。Write-behind
:异步写入模式,是一种先写入缓存,然后异步批量更新到数据库的策略。这种模式可以减少对数据库的写操作次数,适用于写频繁但对即时一致性要求不高的场景。
更多详细情况请阅读:你知道哪几种缓存更新策略?