缓存更新 Cache Aside Pattern
读read 两种情况

第一种 cache hit 缓存命中
应用程序从cache中取数据,取到后返回。
第二种 cache miss 缓存失效
应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
更新update

先把数据存到数据库中,成功后,再让缓存失效。
问题
问题1 为什么是 先更新数据库再删缓存?
假设有两个并发操作,一个是更新update操作,一个读read操作。
此时 缓存和数据库中的数据均为num=1,更新操作将设置num=2。
下面具体看并发操作顺序
update先删除了缓存。 此时情况为:缓存 empty ; 数据库 num=1read读进来,缓存未命中。 此时情况为:缓存 empty ; 数据库 num=1read还是这个读操作,读到了数据库num=1。此时情况为:缓存 empty ; 数据库 num=1。update更新数据库,操作结束。 此时情况为:缓存 empty ; 数据库 num=2read还是读操作,由于刚才读到的数据库num=1,所以设置缓存为num=2,此时情况为:缓存 empty ; 数据库 num=2。
上面这几个顺序不是固定的,只要保证两点
read在update之后,也就保证了,read读不到缓存,必须经过数据库。read操作读到的数据库数据是还未更新的(可能是update还没更新,也可能是主从延迟),就会出现数据不一致。
问题2 为什么是 是删除del缓存而不是设置set缓存?
假设有两个并发更新update操作,先假设为A update 和 B update
此时 缓存和数据库中的数据均为num=1,A update更新操作将设置num=2,B update更新操作将设置num=3。
下面具体看并发操作顺序
A update更新数据库num=2。 此时情况为:缓存 num=1 ; 数据库 num=2。B update另一个并发写请求,更新数据库num=3。此时情况为:缓存 num=1 ; 数据库 num=3。B update这个请求写缓存num=3。 此时情况为:缓存 num=3 ; 数据库 num=3。A update最开始的请求写缓存num=2。此时情况为:缓存 num=2 ; 数据库 num=3。
缓存和数据库数据不一致。如果是del缓存,就不会出现这样的问题。
问题3 如果update更新操作 发生了设置数据库成功,删除del缓存失败。
这种肯定会出现的,可能是网络抖动,应用服务器连接缓存服务器失败、超时等等。
其实如果连接数据库服务器失败的话,是没有问题的,因为数据库和缓存都没有变化,还是一致的。
为了保证删除缓存这个信息传递到缓存服务器,我们可以通过中间件databus等,监听MySQL bin log的方式,一旦MySQL bin log数据有变动,则发出消息,推送到缓存服务器,让缓存服务器删除对应的数据。使用了消息队列,可以保证消息必达。
最佳的方式
- 给缓存设置有效期。
- 删除缓存失败 重试一次,换机器重试等。
问题4 读取操作 主从复制延迟,读到的旧数据
这个就是发生的概率比较大,因为数据库一般都是主从同步的架构,并且存在一定的延迟,还有就是read操作一般都是落在从库上。
解决方法
- 读主库。
- 给缓存设置有效期。
- 通过中间件,列如上面的
databus监听从库的bin log消息,再去触发删除缓存操作。
问题5 Cache Aside pattern 并发问题,我觉得是不太容易理解。
假设有两个并发操作,一个是更新update操作,一个读read操作。
此时 缓存中没有数据,数据库中的数据为num=1,更新操作将设置num=2。
下面具体看并发操作顺序
read读操作,cache miss没有命中缓存。此时情况为:缓存 empty ; 数据库 num=1。- 还是这个
read读操作,读到数据库num=1。此时情况为:缓存 empty ; 数据库 num=1。 update更新操作,更新数据库num=2。 此时情况为:缓存 empty ; 数据库 num=2。- 还是这个
update更新操作,让缓存失效了。 此时情况为:缓存 empty ; 数据库 num=2。 - 还是这个
read读操作,设置缓存为num=1,因为上面步骤2从数据库中读到的是1。此时情况为:缓存 empty ; 数据库 num=2。
此时,数据情况不一致,但是发生的概率较小。
这几个步骤,第1步和最后一步都是read操作,中间是update操作。
一般来讲,update操作要比read操作耗时长。