Cache Aside(旁路缓存)策略
即更新数据时,不更新缓存,而是删除缓存中的数据,在读数据时,发现缓存中没有数据之后,再从数据库中读数据,并更新到缓存中。

这个策略是以数据库中的数据为准,缓存的数据是按需加载的。
读策略的步骤
- 从缓存中读取数据
- 如果缓存中读到数据,则直接返回数据
- 如果缓存不命中,则从数据库中查询数据
- 查询到数据后,将数据写入到缓存中,并返还给用户
写策略的步骤
- 更新数据库中的数据
- 删除缓存记录
问题
能否先删除缓存,在更新数据库?
不行。可能出现数据不一致的情况。

像 Cache Aside 这样先更新数据库,再更新缓存是否有缺陷?
理论上还存在缺陷,有可能造成数据不一致但是这种概率比较低,因为缓存的写入速度远远快于数据库的写入速度。实际过程中,很少出现请求 B 更新了数据库并且清空了缓存,请求 A 才更新缓存的情况。

比如当注册一个新用户,按照这个更新策略,我们需要先写数据库,然后清理缓存(当然缓存中没有数据给你清理)。可当我注册用户后立即读取用户信息,并且数据库主从分离时,会出现主从延迟所以读取不到用户信息的情况。
要解决这个问题的办法,恰恰是更新完数据库后写入缓存,这样后续的请求就能从缓存中读取到数据了。并且因为是新用户,所以不会出现并发更新用户信息的情况。
如果业务对缓存命中率有严格要求,有什么解决方案
Cache Aside 存在的最大问题是当写入叫频繁时,缓存中的数据会被频繁的清理,这样会对缓存命中率造成一定的影响。但是可以考虑以下两种解决方案:
- 在数据更新时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。但是这样做会对性能造成一定影响。
- 同样是在数据更新时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快地过期,对业务影响也可以接受。
Read/Write Through(读穿/写穿)策略
这个策略的核心原则是用户只与缓存打交道,由缓存和数据库通信,写入或者读取数据。
就好比我们在汇报工作的时候,只需要对我的直接上级汇报,再由我的直接上级汇报给他的上级,我是不能越级汇报的。
Write Through 的策略
- 查询要写入的数据在缓存中是否已经存在,如果缓存中数据存在,则更新缓存的数据
- 如果缓存中数据不存在,我们把这种情况叫做 Write Miss(写失效),一般来说,我们可以选择两种 Write Miss 方式:
- Write Allocate(按写分配)
- 🌟 No-write allocate(不按写分配)
做法是写入缓存相应位置,再由缓存组件同步更新到数据库中
做法是不写入缓存中,而是直接更新到数据库中
通常采用的做法,相比按写分配,少了一次缓存写入,能够提升写入的性能。
Read Through 的策略
- 查询缓存中数据是否存在,如果存在则直接返回
- 如果不存在,则由缓存组件负责从数据库中同步更新数据
Read Through / Write Through 示意图

Read Through / Write Through 的特点
- 由缓存节点和数据库打交道
- 相比旁路缓存,这种要少见一点
- 常见的分布式缓存如 Redis、Memcached 都不提供写入数据库或者自动加载数据库的功能
- Write Through 策略中写数据库是同步的,对性能有较大影响。因为相比写缓存,同步写数据库的延迟高很多。接下来介绍的 Write Back 就是异步写数据库策略。
Write Back(写回)策略
核心思想是,写入数据库时,只写入缓存,并把缓存块儿标记为「脏」。而脏块只有被再次使用时才会将其中的数据写入后端存储。
需要注意的是:在 Write Miss(缓存中数据不存在) 的情况下,我们采用 Write Allocate 的方式,在写入数据库的同时写入缓存,后续的更新只需要更新缓存即可。

计算机体系结构中的策略,它的完整读策略是这样的:如果缓存命中,则直接返回;如果缓存不命中,则重新找一个缓存块儿,如果这个缓存块儿是脏的,那么写入后端存储,并且把后端存储中的数据加载到缓存中;如果不是脏的,那么就把后端存储中的数据加载到缓存,然后标记缓存非脏
我们依然可以在一些场景下使用这种策略:在向低速设备写入数据的时候,可以在内存中先暂存一段时间的数据,然后再定时地刷新到低速设备上。
总结
- Cache Aside 是使用分布式缓存的时候最常用的策略,可以在实际工作中使用。
- Read/Write Through 和 Write Back 策略需要缓存组件的支持,所以比较适合你在实现本地缓存组件的时候使用。
- Write Back 策略是计算机体系结构中的策略,不过写入策略中的只写缓存,异步写入后端存储的策略倒是有很多的应用场景。
更具体的,需要结合实际的业务情况:如整体数据量级、访问读写比例、对数据不一致的时间容忍度、对缓存的命中率要求等等做权衡设计。