之前面试,面试官问到了我了缓存更新的问题:更新数据库和更新缓存的逻辑是什么样的?
当时我说的是:先失效缓存,然后再去更新数据库。当时我也说不出个子丑寅卯来,只怪自己用缓存只停留在表面阶段,没有太深挖原理,没有去想缓存的更新策略(平常没接触到这方面),也没有取想缓存更新失败的各种情形。同一个坑不能掉进去两次,这次借这个机会好好梳理一下缓存这块的知识。
缓存的基本概念和用法我就跳过,直接来看缓存的更新策略。
缓存更新的策略有四种:
- Cache aside
- Read through
- Write through
- Write behind
Cache Aside
读数据时:应用先从 cache 中获取数据,没有拿到,则从数据库中获取数据,成功后在放回到缓存中。
更新时:先把数据存到数据库中,成功后再让缓存失效。
这个应该是大家经常用到的模式吧,尤其是在读负载很重的时候。而且在这种模式下,缓存层对于系统来说并不是必须的,缓存挂了,系统还可以直接和持久层交互。
那么为啥叫 Cache Aside 呢?看下面这个图就知道了:
也就是 Cache 放在一边,应用可以同时跟缓存和数据库交互。
这里来画一下流程图,分析一下各种情况下,会出现什么问题。
假设这两个逻辑分别都能原子的执行,那么这种方式是没有啥问题的。问题在于这两种逻辑往往都是并发执行的,这样以来,可能会存在如下的执行逻辑。
在这种情形之下,红色的箭头代表的是执行时间的先后顺序,并不表示它们在同一线程中执行的,这样看来,缓存中其实就存在脏数据了。
有人会说,为啥不在更新数据库的时候,把缓存中的数据也一并修改了呢?
在并发情况下其实也会造成脏数据的产生,因为这是两个写操作,除非你能保证这两个写操作能原子地执行,否则还是有问题:
这不是仍然有脏数据么?
有一个问题就是读的时候写缓存好,还是更新的时候写缓存好?
缓存中出现了脏数据怎么办?
如何避免缓存中出现脏数据?
Read/Write Through Pattern
这种模式其实是将缓存服务作为主要的存储,应用的所有读写请求都是直接与缓存服务打交道,而不管最后端的数据库了,数据库的服务器由缓存服务来更新和维护。不过缓存中数据变更的时候是同步去更新数据库的,在应用眼中只有缓存服务。
这种策略的好处是出现脏数据的概率比较低,但是会对缓存强依赖,对缓存服务的稳定性要求较高。另外,增加缓存节点还会有初始状态空数据问题。
Read Through 的 through 则是通过的意思:
Write Throught 也是如此:
Write Behind
这个模式是 Read/Write Through 策略的变种,区别就是 Read/Write Through 模式的缓存写数据是同步的,而 Write Behind 模式的缓存操作数据库是异步的。
这种模式的特点是速度快,效率非常高,但是数据的一致性比较差,可能会有数据丢失的情况,实现逻辑也较为复杂。
Write Behind 不是立即写库,而是异步的有延迟。这种设计对于写负担很重是很有帮助的。
总结
这里介绍了不同的缓存策略,以及它们各自的优劣势。在实际项目中,需要仔细评估一下你的目标,理解数据的访问模式,选择最合适的策略或者组合。