架构误区系列 7:删除 + 更新的缓存刷新机制
缓存是我们在架构中比较常用的一种基础组件,目的是为了提升后端的查询效率,控制对于存储的访问量保证数据库等基础设施的稳定。
但是,在缓存的使用上,部分架构师过于强调数据库和缓存数据一致性,做了很多多余的设计,不但加大了软件的复杂度,在一些特殊场景下反而导致了一些非预期异常的出现。
最近在 CR 中发现,开发的同学为了提升数据库和缓存的一致性,设计了一个删除+更新的缓存刷新逻辑:
修改数据的进程在修改完数据后,删除缓存。
数据读取方查询缓存未命中时,刷新缓存。
仔细琢磨一下这个设计,其实在刷新频繁的场景下是存在 bug 的。我们考虑如下场景:
更新者 A 更新数据,删除缓存。
更新者 B 更新数据,删除缓存。
读取放 C 在两者之间读取数据。
在极端场景下,C 在 A 删除数据之后读取,发现缓存为空,开始启动缓存刷新机制。读取数据库拿到 A 更新后的数据。在写入缓存之前,B 更新了数据并删除了缓存。这个时候,load 到缓存中的数据是 A 的版本而安非 B 的版本。在缓存失效前,缓存中都是“脏数据”。而在这种删除+更新的逻辑下,一般缓存的失效时间会设置的比较大。
这种删除+更新的设计,其实是一个典型的过度设计。
首先,我们需要充分意识到数据库和缓存是两个组件,除非在两者之间开启分布式事务,否则保证两者的强一致性是不可能的。在数据库和缓存之间开启分布式事务做数据同步,最简单的方式就是在写入数据的售后开启分布式锁,这种方式降低了数据更新的并发度,损失基本大于收益。
数据库和缓存之间,最可行的就是保证最终一致性,或者线形一致性。数据库数据修改最终能同步到缓存,缓存不会将新数据更新成老数据,基本上就可以达到两者一致性的要求。
一种方式的写入方更新缓存。写入的同时用注册缓存更新任务。缓存更新时间判断缓存版本,避免乱序更新。
另一种方式是缓存过期+读方更新。读取方在没有数据的情况下去更新缓存,缓存在一定时期后过期。这种方式需要比较短的过期时间,否则在过期前缓存是不会被更新的。
不管哪种方式,都由一方对缓存的刷新负责。
版权声明: 本文为 InfoQ 作者【agnostic】的原创文章。
原文链接:【http://xie.infoq.cn/article/26253707c42bb168c8e9595ba】。文章转载请联系作者。
评论