一、什么是一致性问题
为了提升服务的性能,我们一般会把热点放进缓存,那么这些热点数据就同时存在于数据库和缓存中,缓存中的数据和数据库中的数据要保持一致,这便是缓存一致性问题。
二、使用缓存存在的问题
加了缓存之后,读写流程大概如下:
1. 读流程
2. 写流程
3. 写操作的应该更新缓存还是删除缓存
答案是应该删除缓存,如果是更新,可能会有如下问题:
如上图所示,如果线程 A 先更新了 DB,接着线程 B 更新 DB,正常情况,缓存中保存的应该是线程 B 写入 DB 的数据,但因为最后是线程 A 去更新的缓存,因此导致 DB 和缓存中的数据不一致。
4. 既然要用删除,删除操作在更新DB之前还是之后呢
答案是之前,如果是之后,可能会出现如下问题:
如果先更新 DB,在更新了 DB 之后,还没来得及删除缓存之前,线程 B 读请求进来了,那么读取到的是缓存中的旧数据,也会出现一致性问题。
5. 先删除再操作DB就没问题吗
答案是也会有问题,可能会出现如下场景:
线程 A 先删除了缓存,还没来得及更新 DB 的时候,线程 B 进来了,把 DB 中的旧数据又读取到了缓存中,最后线程 A 更新了 DB,数据还是不一致。
三、缓存一致性问题的解决方案
1. 双删延迟策略
上面说了先删除缓存还是会存在问题,就是线程 A 更新 DB 之前如果线程 B 把数据读到缓存中了,数据也会不一致。双删延迟策略就是更新了 DB 后休眠一段时间再次删除缓存,如下:
为什么要休眠一段时间?
休眠是为了让线程 B 读请求能够执行完。如果不休眠,可能会出现第二步的时候线程 B 从 DB 读取到旧数据了,还没来得及更新到缓存中时,线程 A 执行了第四步和第五步,然后线程 B 才执行第三步,又把刚才读取到的旧数据更新到缓存中了。因此,休眠的时间应该根据线程 B 执行时间而定。存在的问题
问题很明显,第二次删除要休眠一段时间,用户体验不好,并且对业务也有入侵。
2. 使用binlog异步删除缓存
我们知道,DB 的操作一般都会记录到日志中,比如 MySQL,所有的写操作都会记录到 binlog 中,那么我们可以通过 binlog,去删除缓存。canal 就是一款用来解析数据库日志的工具,我们可以接入 canal,采集到日志,然后通过 MQ 异步的去删除缓存,这样对业务代码就没有入侵了。异步删除缓存流程如下:
如果是 DB 有主从库怎么办?
主从可能会出现的情况就是主库的数据还没来得及同步到从库的时候,消费者已经把缓存给删除了,然后读请求进来,读取到了从库的脏数据,更新到了缓存中,还是有一致性问题。其实解决办法很简单,canal 采集从库的 binlog 就行了。canal 的原理
我们知道 MySQL 主从的原理就是三个线程,一个线程负责记录主库的写操作,也就是 binlog 线程;一个线程负责把主库的 binlog 拉取到从库;还有一个线程就负责读取 binlog,完成从库数据的同步。canal 就是利用这一点,伪装成一个从库,从而读取到 binlog。