Cache-Aside pattern
- 失效:应用程序先从
cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。 - 命中:应用程序从
cache 中取数据,取到后返回。 - 更新:先把数据存到数据库中,成功后,再让缓存失效。
另外,知名社交网站
这种情况不存在并发问题么?
不是的。假设这会有两个请求,一个请求
- 缓存刚好失效
- 请求
A 查询数据库,得一个旧值 - 请求
B 将新值写入数据库 - 请求
B 删除缓存 - 请求
A 将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
假设,有人非要抬杠,有强迫症,一定要解决怎么办?
如何解决上述并发问题?
首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。
还有其他造成不一致的原因么?
有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。
如何解决?
提供一个保障的重试机制即可,这里给出两套方案。
方案一
如下图所示
流程如下所示
- 更新数据库数据;
- 缓存因为种种问题删除失败
- 将需要删除的
key 发送至消息队列 - 自己消费消息,获得需要删除的
key - 继续重试删除操作,直到成功
然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的
方案二:
流程如下图所示:
更新数据库数据
- 数据库会将操作信息写入
binlog 日志当中 - 订阅程序提取出所需要的数据以及
key - 另起一段非业务代码,获得该信息
- 尝试删除缓存操作,发现删除失败
- 将这些信息发送至消息队列
- 重新从消息队列中获得该数据,重试操作。
Cache-Aside pattern
http://ysocket.pages.dev/2022/03/12/Cache-Aside-pattern/