分布式之数据库和缓存双写一致性方案解析(二)

引言

该文是对《分布式之数据库和缓存双写一致性方案解析》,一文的补充。博主在该文中,提到了这么一句话

应该没人问我,为什么没有先更新缓存,再更新数据库这种策略。

博主当时觉得,这种更新策略比较简单,没必要多做说明,结果太多人留言给博主,问我为什么不说这套方案?好吧,博主先跟大家道个歉,是我的问题。所以再开一文,把这个方案说明一下

正文

下面说明一下先更缓存,再更新数据库这套方案
更新数据库失败了怎么办?
这个问题其实很好解决,提供一个补偿措施即可。这个补偿措施,大家灵活变通,博主只是举例,如下图所示:
分布式之数据库和缓存双写一致性方案解析(二)
流程如下所示
(1)更新缓存数据;
(2)更新数据库失败
(3)将需要更新的sql发送至消息队列
(4)自己消费消息,获得需要更新的sql
(5)继续重试更新操作,直到成功
其他方案不列举,因为重点不在这,在下面的情况
有存在其他的线程安全问题么?
有的,假设这会同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了缓存
(2)线程B更新了缓存
(3)线程B更新了数据库
(4)线程A更新了数据库
请求A更新数据库应该比请求B更新数据库早才对,但是因为网络等原因,B却比A更早更新了数据库。这就导致了脏数据,因此不考虑。
可是,这时候有一个细心的读者,给博主举了一个反例。该例子出自《从P1到P7——我在淘宝这7年》这篇博客,
博主偷个懒,直接贴一下该博客的原话

在【招财进宝】项目中有一个技术的细节值得拿出来说说,淘宝商品详情页面每天的流量在10亿以上,里面的内容都是放在缓存里的,做【招财进宝】的时候,我们要给卖家显示他的商品被浏览的次数,这个数字必须实时更新,而用缓存的话一般都是异步更新的。于是商品表里面增加了这样一个字段,每增加一个PV这个字段就要更新一次。发布上去一个小时数据库就挂掉了,撑不住这么高的update。数据库撑不住怎么办?一般的缓存策略是不支持实时更新的,这时候多隆大神想了个办法,在apache上面写了一个模块,这个数字根本不经过下层的web容器(只经过apache)就写入一个集中式的缓存区了,这个缓存区的数据再异步更新到数据库。好像什么问题,到了多隆手里,总能迎刃而解。

好吧,如果没耐心的读者,直接看博主的总结吧。上面巴拉巴拉一堆,就是说,当时他们有一个读多写多的场景,然后多隆大神用了先更缓存,再异步更新数据库的策略。
难道淘宝的大神没发现线程安全问题?
不是的,上面提到的场景具有一个特殊性。我们先摘取关键一句话

于是商品表里面增加了这样一个字段,每增加一个PV这个字段就要更新一次

ps:PV是page view,页面浏览量的意思。
博主斗胆猜测,他们做的应该是用户每次点击,数据库里的这个字段就加一的操作。
那我们这时的SQL一般是这么写

update product_tb set number = number+1 where product_id =xxx

大家注意到了么,并发执行这句SQL并不需要关心执行顺序。哪个更新线程先执行加一的SQL语句 ,与操作顺序有什么关系呢?
再说的通俗一点,假设我们同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了缓存
(2)线程B更新了缓存
(3)线程B更新了数据库
(4)线程A更新了数据库
因为他们这个时候执行的sql是无序的,所以上面的步骤(3)和步骤(4)哪一个步骤先执行,并没有关系。最终结果一定是一致的。
容博主啰嗦,来个实例,假设表product_tb如下

product_id number
1 3

这时请求A和请求B同时对product_id为1的数据进行更新操作,无论是按出现并发问题时的顺序
(1)线程B更新了数据库,进行加一
(2)线程A更新了数据库,进行加一
还是正常的顺序
(1)线程A更新了数据库,进行加一
(2)线程B更新了数据库,进行加一
最终结果都是

product_id number
1 5

ok。说到这里,大家应该是懂了。换句话说,如果此时,操作的sql是有序的,就会出现最上面说的线程安全问题。所以,希望大家针对问题多思考总结。
给大家留一个思考问题?
如果此时是一个读多写多的场景,又要求更新数据库的操作必须严格保证顺序,那这个时候怎么保证缓存和数据库的一致性?大家可以来我的博客留言。

总结

本文是对上次文章的一次文章的一次补充。只怪博主思考问题太过简单,给大家留了个坑。因此再开一篇文章进行补充说明。希望大家能够有所收获。

分布式之数据库和缓存双写一致性方案解析(二)

上一篇:“全栈2019”Java第一百零四章:匿名内部类与外部成员互访详解


下一篇:分布式之数据库和缓存双写一致性方案解析(三)