缓存模式
常用的缓存模式有:
- cache-aside
- read-through
- write-through
- write-back
- refresh-ahead
Cache Aside
Cache Aside:
- 把 Cache 当成一个普通的数据源
- 更新 Cache 和 DB 都依赖于开发者自己写代码
业务代码可以做决策:
- 未命中的时候是否要从 DB 取数据。如果不从 DB 取,可以考虑使用默认值进行业务处理
- 同步 or 异步读取数据并且写入
- 采用 singleflight
要点:业务代码会同步从数据库读取数据,而后用 DB 数据来执行业务,同时异步刷新缓存。
要点:业务代码在发现缓存没有返回数据的时候,会直接返回响应,或者使用默认值。而后异步从 DB 读取数据,刷新缓存。
read-through 和 write - through 也会有这种问题。
Read Through
Read Through:
- 业务代码只需要从 cache 中读取数据,cache 会在缓存不命中的时候去读取数据
- 写数据的时候,业务代码需要自己写 DB 和写 cache
cache 可以做决策:
- 未命中的时候是否要从 DB 取数据。如果不从 DB 取,可以考虑使用默认值进行业务处理
- 同步 or 异步读取数据并且写入
- 采用 singleflight
要点:cache 会同步从数据库读取数据,而后直接返回值,同时异步刷新缓存。
要点:cache 会直接返回响应,而后异步从 DB 读取数据,刷新缓存。
read - through 对用户来说,基本上只能是一个缓存类型一个实例。
例如 userCache := &ReadThroughCache
这是因为 LoadFunc 在大多数时候,我们都没办法把它写成非常通用的。
在这个例子里面,从数据库中找 user,或者找 order,明显就是不一样的。
Read Through 泛型
于是我们会考虑使用泛型。虽然看上去和 ReadThroughCache 没什么区别。
实际上,ReadThroughCacheV1 并没有实现 Cache 接口。
在这两个版本里,大家都可以控制住在 Get 里面,当 err 为 KeyNotFound 的时候,是同步运行,还是异步执行。
Write Through
Write Through
- 开发者只需要写入 cache,cache 自己会更新数据库
- 在读未命中缓存的情况下,开发者需要自己去数据库捞数据,然后更新缓存(此时缓存不需要更新 DB 了)
cache 可以做决策:
- 同步 or 异步写数据到 DB,或者到 cache
- cache 可以自由决定先写 DB 还是先写 cache,一般是先写 DB
要点:cache 会同步将数据刷新到 DB,而后返回响应,同时异步刷新缓存。
要点:只具备理论意义,实际上几乎不会用。
WriteThroughCache 和 ReadThroughCache 差不多,只是 LoadFunc 改成了 StoreFunc。
类似地,WriteThroughCache 也可以尝试使用泛型,但是也面临着 ReadThough 的问题。
也可以在这两个版本里面,控制同步还是异步刷新缓存。
Write Back
Write Back
- 在写操作的时候写了缓存直接返回,不会直接更新数据库,读也是直接读缓存
- 在缓存过期的时候,将缓存写回去数据库
优缺点:
- 所有 goroutine 都是读写缓存,不存在一致性的问题 (如果是本地缓存依旧会有问题)
- 数据可能丢失:如果在缓存过期刷新到数据库之前,缓存宕机,那么会丢失数据
write-back 主要是利用 OnEvicted 回调,在里面将数据刷新到 DB 里。
Refresh Ahead
refresh-ahead 依赖于 CDC(changed data capture) 接口:
- 数据库暴露数据变更接口
- cache 或者第四方在监听到数据变更之后自动更新数据
- 如果读 cache 未命中,依旧要刷新缓存的话,依然会出现并发问题
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。