MelonBlog

在应用层如何保障缓存的一致性

一般在做应用的时候,我们都会在DB层上面架一个缓存层,或者直接把缓存集成在业务代码里。anyway,无论把缓存放到哪一层,目的都是为了减少DB层的压力,因为应用层我们很容易做成集群,但是DB做成集群的复杂度比较大。用缓存架在DB上层,既可以减轻DB的负载,也可以增加应用的业务吞吐率。

但是,缓存和DB之间如何同步数据,是一个比较棘手的问题,如果缓存和DB数据不一致,访问缓存的效率确实提高了,但是业务可能会出错,在某些重要的业务场景,是没办法容忍这种错误出现的。所以今天来聊聊如何能够优雅的做好缓存和DB之间的数据一致性。

缓存的更新策略

当一条数据需要更新时,我们是先更新DB还是先更新缓存呢?这里涉及到了缓存的更新策略,不同的缓存更新策略,在保障数据的一致性方面有不同的差异。


Write Through:用同步的方式,先更新DB,再更新Cache

Write Behind:先更新Cache,然后用异步的方式更新DB

Write Invalidate:以同步的方式,先更新DB,然后让Cache失效(和Write Through的区别是这次不直接更新Cache)

Refresh Ahead:预测热点数据,提前把热点数据Cache起来,然后定期更新Cache


我们可以分析一下这几种方式的优缺点。

Write Through:非常安全,不会导致数据丢失,因为是先更新DB,但是安全的代价是同步操作的性能比较差,因为这种场景还得需要配合锁一起来使用,防止其他线程读取到了旧的Cache数据,要等到DB和Cache都更新完,这中间可能会因为 磁盘I/O或者Socket I/O 导致花费非常多的时间,高并发场景一般很难接受这种方案。

Write Behind:性能确实比Write Behind好一些,但是这种方案不够安全,例如,如果更新完Cache之后,程序直接挂掉了,那DB就没办法同步到这条数据的更新操作。

Write Invalidate:Write Through的优化方案,同步等待的时间更短一些,因为少了更新Cache这一步,但是这意味着如果更新操作比较频繁,那么每次读取的缓存命中率就很低,因为要经常去数据库里读取最新的数据,在更新次数多的场景下,这种方案不完美。

Refresh Ahead:适合小批量数据,并且需要提前知道热点数据,这个策略的使用场景有一点苛刻,只能针对某些固定数据做缓存。

没有完美的方案

在了解了这几种缓存策略之后, 我们可以发现,没有任何一种方案总是完美的,不同的策略都有不同的优缺点。

在大部分场景下Refresh Ahead 是和 Write Through | Write Behind | Write Invalidate 配合一起用的,那这3种写方案,具体用什么,更多的是根据业务场景来选择。


如果应用的并发量较小,可以直接使用Write Through, 因为这种方案既安全,又很简单,实现起来的复杂度很小。
如果是并发量很大,并且对性能要求非常高的场景,用Write Behind方案,性能最好,并且可以配合WAL(Write Ahead Log )尽可能防止出现数据丢失。
在并发量和性能要求都比较折中的情况下,可以使用Write Invalidate,这是一个比较“中庸”的方案,实际上我们遇到的大部分业务场景,对性能要求都不是非常苛刻,Write Invalidate可以胜任大部分常规场景。