
一.最经典的数据库加缓存的双写双删模式
1.1 Cache Aside Pattern概念以及读写逻辑
(1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
(2)更新的时候,先删除缓存,然后再更新数据库
1.22、为什么是删除缓存,而不是更新缓存呢?
原因很简单,很多时候,复杂点的缓存的场景,因为缓存有的时候,不简单是数据库中直接取出来的值,可能需要比较复杂的计算,甚至进行很多网络请求以及DB请求(比如我们有个缓存就是查微信的公共库以及我们自己的私有库联合组成一个缓存),这种更新缓存的代价很高的,但是呢我们更新完了缓存这个缓存,这个缓存也不一定立马就有人用,可能我更新了很多次数据库更新了很多次缓存都没人访问,这就导致了我服务器做了很多无用的计算
二. 高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计
这里围绕和结合实时性较高的库存服务,把数据库与缓存双写不一致问题以及其解决方案,给大家讲解一下.
我们有两个操作顺序可以选择,其中都存在各种双鞋不一致情况,具体讨论讨论
- 更新数据先删除缓存,再更新数据库
- 更新数据先更新数据库,再删除缓存
2.1先删除缓存再更新数据库方式
2.1.1 上面说的最经典的方式有什么缓存不一致的问题?解决方案是什么?
问题:如果我们的方案是先修改数据库库存,再删除缓存,那么如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致
解决思路:
先删除缓存,再修改数据库,如果删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致,因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中
注意这里无并发读写没问题,但是并发情况下依然会有问题,我们继续往下看
2..22 上面第一个解决方案在并发下还是有问题
如果先删除缓存再删除数据库可能存在这种情况
- A服务删除缓存成功
- B请求来了读旧数据库存
- A更新新的库存成功
这样依然是数据库和缓存的库存不一致了
2.3 如果允许短暂的不一致,我们可以用什么思路来做?
2.3.1 基于MQ的分布式事务实现最终一致性

2.3.2 基于binlog监听实现

2.3.3 延迟双删 (比上面稍微优点的一点在于这里不需要印入MQ)
延时双删
延时双删的方案的思路是,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再 Sleep 一段时间,然后再次删除缓存。
Sleep 的时间要对业务读写缓存的时间做出评估,Sleep 时间大于读写缓存的时间即可。
流程如下:
线程1删除缓存,然后去更新数据库。
线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存。
线程1,根据估算的时间,Sleep,由于 Sleep 的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除。

三 **高并发下又要求强一致性的解决思路
3.1 **将统一商品的缓存操作进行串行化,通过 MQ 串行化写请求实现强一致
适用于订单、库存等强一致业务场景。
所有写操作不再直接写库,而是通过 MQ 队列串行消费,消费者统一处理更新数据库 + 清理缓存。
- 应用将写操作封装为消息发送到 MQ
- 消费者串行消费消息
-
消费者更新数据库后删除缓存
优点:
- 通过串行消费避免并发写覆盖
- 可控性强,可与分布式事务或补偿机制配合
缺点:
- 实现复杂,对架构设计要求高
- 增加消息队列运维成本
- 需处理消息幂等、失败重试
3.2 分布式锁
在可能进行缓存变更的地方加锁
比如我们读取一个文章,更新文章需要对文章id加锁,如果读取缓存没读取到,现在要把数据库数据放到缓存里,那么也需要先获取锁才能进行更新。
这样就可以保障缓存操作的串行化了。
