缓存更新
缓存(Redis、memory cache等)被广泛应用于高并发、高性能的项目中。应用在,请求先查询缓存,命中则返回。未命中则查询数据库,并缓存。而且缓存也有过期时间的,避免浪费内存、出现不一致等,因此缓存是最终一致性的。但实际使用中,需要主动更新缓存。那就存在一个问题:为保证数据读取的正确性、一致性,是先更新缓存,还是先更新数据库?
数据变化时,缓存可以更新也可以删除,等查询的时候再次缓存即可。因此可以组合出以下四种策略:
先更新缓存,再更新数据库;
先删除缓存,再更新数据库;
先更新数据库,再更新缓存;
先更新数据库,再删除缓存;
上述策略都是两步操作完成。下边依次来分析这四种策略的可行性。
先更新缓存,再更新数据库
两步:第一步更新缓存,第二步更新数据库。
场景
1.线程A 尝试更新数据库,第一步更新缓存
2.线程B 尝试更新数据库,第一步更新缓存
3.线程B 第二步更新数据库
4.线程A 第二步更新数据库
此时,因为线程B 在线程A之后完成了缓存更新,但先更新了数据库。即 A-B-B-A,导致缓存中是线程B 设置的数据。在一个Ttl内,数据不一致。
先删除缓存,再更新数据库
场景
1.线程A 尝试更新数据库,第一步更新缓存
2.线程B 读缓存,未命中,继续读数据库,并缓存。(此时缓存的是旧数据)
3.线程A 第二步更新数据库
此时,在一个Ttl 内,缓存内的数据是旧数据。出现了数据不一致。
先更新数据库,再更新缓存
场景
1.线程A 尝试更新数据库,第一步 更新数据库
2.线程B 尝试更新数据库,第一步 更新数据库
3.线程A 第二步更新缓存
4.线程A 第二步更新缓存
此时,也是 A-B-B-A 的问题,导致缓存中是线程A 设置的数据。在一个Ttl内,数据不一致。
接下来看先更新数据库,再删除缓存。
先更新数据库,再删除缓存
这个也就是老外提出的《Cache-Aside pattern》。
场景
1.线程A 尝试 更新数据库,第一步 更新数据库,第二步删除缓存
2.线程B 尝试 读取 缓存,未命中,读取数据库,并缓存
此时,数据一致。这个在网上的facebook的论文《Scaling Memcache at Facebook》也是选择这种策略
可能有人有疑问了:不会并发么?确实存在的:
场景
前提:缓存中无数据
1.线程A 尝试读取,未命中缓存,读数据库
2.线程B 尝试 更新数据库
3.线程B 删除缓存
4.线程A 把读取的数据更新到缓存
此时,也是 A-B-B-A 的问题。
确实,不过这里需要说明一下:这种情况发生的概率比较低!!!
我们常用的数据库mysql,包括oracle,更新操作耗时比较长,查询操作耗时比较短。如果要达到,需要满足 步骤1 的耗时比 步骤2+步骤3 的耗时长。这几乎不可能的。线程A在 步骤1和步骤4之间做了其他事情。那就是逻辑问题了。我们实际使用中,采用这种策略就能满足需要了。
可能有人要问了:
上述并发问题有办法解决么?
有:可以采用异步延时删除策略,此时在 更新完数据库 到 删除缓存这段时间,数据可能稍微不一致。
有其他可能情况不一致么?
有:更新缓存失败!
方案:消息重试,可以接dts消息(binlog),这样缓存删除失败后,可以消息重试。
本文探讨了在数据变化时,缓存更新的四种策略:先更新缓存再更新数据库、先删除缓存再更新数据库、先更新数据库再更新缓存以及先更新数据库再删除缓存。通过分析并发场景,指出先更新数据库再删除缓存的策略(Cache-Aside pattern)在实际应用中能较好地保证数据一致性,虽然可能存在并发问题,但可以通过异步延时删除策略和消息重试机制进行优化。
1131

被折叠的 条评论
为什么被折叠?



