7.高级应用
7.1.数据库和缓存双写一致性
如果更新了数据库中的数据, 在缓存中的数据如何保持一致性?
4种方案 :
- 先写缓存, 再写数据库
- 先写数据库, 再写缓存
- 先删缓存, 再写数据库
- 先写数据库, 再删缓存
7.1.1.先写缓存, 再写数据库
先将更新的数据写入缓存, 再写入到数据
可能出现的异常 : 写入缓存成功 , 但写入数据库出现异常.
这种情况在缓存中出现"假数据",
**这种方法在实际工作中通常不用
7.1.2.先写数据库, 再写缓存
先写入数据库, 再写入缓存. 在低并发环境下或者数据不会引发太大的影响, 可以使用
7.1.2.1.异常情况一
请求a 写完数据库, 由于网络原因, 没有及时写入缓存
之后, 请求b 顺利完成写入数据库及缓存操作
此时缓存中存放的是新的数据
但请求a 在之后又完成了 ,写缓存的操作,
导致缓存中存放的是旧数据
7.1.2.2.写入缓存的操作比较复杂
如果存入缓存的数据得到比较复杂, 频繁的写入是比较消耗资源的
对于 写多 读少 的业务 就会很多问题
7.1.3.先删除缓存, 再写数据库
实现业务里, 可以不及时更新缓存, 而是等到查询里再去更新缓存
7.1.3.1.高并发下的问题
客户b 第7步写入的数据库里的旧数据, 没有包含客户a 新操作写入的新数据
7.1.3.2.缓存双删
在 上面的基础上 , 当 客户a 完成写入数据库一段时间后 , 再次将 缓存清空.
这里 一定要等一段时间, 因为太快, 可能出现 第9 步在 第7步之前执行 , 就失去意义了,
所以一定要等一段时间, 如: 500ms
一个新的问题, 如果 第9步 执行出错了怎么办?
7.1.4.先写数据库, 再删除缓存
有将数据写入到数据库后, 再删除缓存
但 如果有两个操作, 一个是写, 另一个是读, 情况如下
7.1.4.1.情况一 : 先写后读
先写, 后读, 但写入数据库后 , 再写入缓存时卡顿, 会导致读到旧数据
这个时候有问题, 但影响不是很大
7.1.4.2.情况二 : 先读后写
这种情况通常没有问题, 如果
缓存过期时间到了,自动失效。
请求b 查询缓存,发缓存中没有数据,查询数据库的旧值,但由于网络原因卡顿了,没有来得及更新缓存。
请求a 先写数据库,接着删除了缓存。
请求b 更新旧值到缓存中。
这种情况发生的可能性很少.
7.1.5.删除缓存失败怎么办?
7.1.5.1.定时
-
当用户操作写完数据库,但删除缓存失败了,需要将用户数据写入重试表中。如下图所示:
-
在定时任务中,异步读取重试表中的用户数据。重试表需要记录一个重试次数字段,初始值为0。然后重试5次,不断删除缓存,每重试一次该字段值+1。如果其中有任意一次成功了,则返回成功。如果重试了5次,还是失败,则我们需要在重试表中记录一个失败的状态,等待后续进一步处理。
-
在高并发场景中,定时任务推荐使用elastic-job。相对于xxl-job等定时任务,它可以分片处理,提升处理速度。同时每片的间隔可以设置成:1,2,3,5,7秒等。
7.1.5.2.MQ
-
当用户操作写完数据库,但删除缓存失败了,产生一条mq消息,发送给mq服务器。
-
mq消费者读取mq消息,重试5次删除缓存。如果其中有任意一次成功了,则返回成功。如果重试了5次,还是失败,则写入死信队列中。
-
推荐mq使用rocketmq,重试机制和死信队列默认是支持的。使用起来非常方便,而且还支持顺序消息,延迟消息和事务消息等多种业务场景。
–7.1.5.3.binlog
监听binlog,比如使用:canal等中间件。