如何实现缓存一致性面试须知
问题产生的来源
分布式环境和高并发环境都会导致缓存的不一致性,主要由于网络环境的复杂性及延时性导致。
文章的参考链接
目前主要的解决方法
先删除缓存,再更新数据库
比如A线程,先删除了缓存,但是还没有更新数据库。然后B线程这个时候却进行查询,因为缓存中没有查到,然后就去查询数据库的值,并将其写入了缓存,然后A线程这个时候却写入了数据库,那么就会导致缓存不一致的问题。
1.解决方案
延迟双删方案
即A线程进行修改数据库中的值时,第一次删除缓存,更新数据库。然后直接睡眠一会了,睡眠一个B线程查询数据库,再加写入缓存的时间。这样B线程更新缓存后,A线程会继续进行一个删除。
问题点
- 吞吐量会有问题,睡眠的时间会白白增加用户的等待时间
- 二次删除万一失败了呢。不过这个可能性很低
先更新数据库,再删除缓存
问题点
1.更新数据库,后删除缓存失败了,后续的查询就会导致不一致的问题。不过这个出现问题的几率会比先删除缓存,再更新数据库导致出现缓存不一致的几率要小。为什么呢?因为先删除缓存,再更新数据库,只要并发量比较多,就会出现缓存不一致的情况。这个就算从更新数据库,到删除缓存的期间。就算期间有人查询了,也只会导致那一段时间的人查询出现问题。而且出现这个问题的情况很小,因为操作redis直接操作内存。比查询数据库快多了。
2.假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。假设,有人非要抬杠,有强迫症,一定要解决怎么办?
流程如下所示(1)更新数据库数据;(2)缓存因为种种问题删除失败(3)将需要删除的key发送至消息队列(4)自己消费消息,获得需要删除的key(5)继续重试删除操作,直到成功然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
解决方案
1.引入消息中间件
每次数据库进行修改时,进行发q,消费端获取消息时,缓存进行删除。下次查询的时候直接查询到数据库,再写入缓存。目前我公司就是这样做的。这样做也只是保证最终一致性,而且消费丢失,或者消费机出现问题的时候也会导致缓存不一致问题。这个时候我们通常是手动处理,去清除缓存。
2.启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作
总结
我只是对网上的一些方案进行总结,学习了别人的思路,然后稍微总结了一下。后续我遇见什么好的方案我都会补充到自己的帖子中。对于通过mysql的binlog去实现缓存一致性。我会另起一篇帖子进行编写。