缓存与数据库一致性系列-04

Posted by Kido on 2018-12-09

经过前三篇的讨论,我们终于的实现了“最终一致性”,那今天呢,我们再进一步,在前一个方案的基础上实现“强一致性

强一致性,包含两种含义:

  • 缓存和DB数据一致
  • 缓存中没有数据(或者说:不会去读缓存中的老版本数据)

首先我们来分析一下,既然已经实现了“最终一致性”,那它和“强一致性”的区别是什么呢?没错,就是“时间差”,所以:

最终一致性方案” + “时间差” = “强一致性方案

那我们的工作呢,就是加上时间差,实现方式:我们加一个缓存,将近期被修改的数据进行标记锁定。读的时候,标记锁定的数据强行走DB,没锁定的数据,先走缓存

写流程:

我们把修改的数据通过Cache_0标记“正在被修改”,如果标记成功,则继续往下走,后面的步骤与上一篇是一致的《缓存与数据库一致性系列-03》那如果标记失败,则要放弃这次修改。

何为标记锁定呢?比如你可以设定一个有效期为10S的key,Key存在即为锁定。一般来说10S对于后面的同步操作来说基本是够了~

如果说,还想更严谨一点,怕DB主从延迟太久、MQ延迟太久,或Databus监听的从库挂机之类的情况,我们可以考虑增加一个监控定时任务
比如我们增加一个时间间隔2S的worker的去对比以下两个数据:

  • 时间1: 最后修改数据库的时间

VS

  • 时间2: 最后由更新引起的’MQ刷新缓存对应数据的实际更新数据库’的时间

数据1: 可由步骤1.1获得,并存储
数据2: 需要由binlog中解析获得,需要透传到MQ,这样后面就能存储了
这里提一下:如果多库的情况的话,存储这两个key需要与库一一对应

如果 时间1 VS 时间2 相差超过5S,那我们就自动把相应的缓存分片读降级。

读流程:

先读Cache_0,看看要读的数据是否被标记,如果被标记,则直接读主库;如果没有被标记,后面的步骤与上一篇是一致的(《缓存与数据库一致性系列-03》)。

方案分析

优点剖析

1. 容灾完善

我们一步一步来分析:

写流程容灾分析
  • 写1.1 标记失败:没关系,放弃整个更新操作
  • 写1.3 DEL缓存失败:没关系,后面会覆盖
  • 写1.5 写MQ失败:没关系,Databus或Canal都会重试
  • 消费MQ的:1.6 || 1.7 失败:没关系,重新消费即可
读流程容灾分析
  • 读2.1 读Cache_0失败:没关系,直接读主库
  • 读2.3 异步写MQ失败:没关系,缓存为空,是OK的,下次还读库就好了

2. 无并发问题

这个方案让“读库 + 刷缓存”的操作串行化,这就不存在老数据覆盖新数据的并发问题了

缺点剖析

1. 增加Cache_0强依赖

这个其实有点没办法,你要强一致性,必然要牺牲一些的。
但是呢,你这个可以吧Cache_0设计成多机器多分片,这样的话,即使部分分片挂了,也只有小部分流量透过Cache直接打到DB上,这是完全是可接受的

2. 复杂度是比较高的

涉及到Databus、MQ、定时任务等等组件,实现起来复杂度还是有的

方案总结

OK,到此呢,我们已经实现了“数据库和缓存强一致性”,这个系列就先这样啦,等我学到了更好的方案,再来分享~

这里还是要提一下:一致性的要求根据自己的业务决定就好,适合的才是最好的

后记

如果对你有帮助,那就再好不过了~

参考文献

1. Canal
2. Databus
3. Kafka
4. Redis Expire 命令

相关文章链接

  1. 《缓存与数据库一致性系列-序言》
  2. 《缓存与数据库一致性系列-01》
  3. 《缓存与数据库一致性系列-02》
  4. 《缓存与数据库一致性系列-03》

-->