Redis-缓存一致性
持久化层和缓存层的一致性问题也通常被称为双写一致性问题,“双写”意为数据既在数据库中保存一份,也在缓存中保存一份。
对于一致性来说,包含强一致性和弱一致性,强一致性保证写入后立即可以读取,弱一致性则不保证立即可以读取写入后的值,而是尽可能的保证在经过一定时间后可以读取到,在弱一致性中应用最为广泛的模型则是最终一致性模型,即保证在一定时间之后写入和读取达到一致的状态。
最终缓存一致性
# Cache-Aside
读请求:
- 访问缓存, 命中直接返回。 (命中只有一步)
- 访问缓存未命中,查询数据, 将查询结果更新到缓存中。 (未命中有三步)
写请求: 先更新数据,在删除缓存。 (两步)
问题:
写请求为什么是删除缓存,不是更新缓存
- 性能考虑:如果写操作性能开销比较大,并且有大量的写请求,可能刚更新的缓存没读到又被其他缓存更新了(缓存扰动),导致缓存利用率不高。
- 安全性考虑: 两个写线程并发访问, 前一个请求先更新数据库, 后一个请求更新数据库和缓存, 前一个请求在更新缓存。 这样导致了 缓存 和 数据库 数据不一致了
为什么先更新数据库, 而不是先删除缓存
- 性能考虑: 先删除缓存, 由于缓存中数据缺失, 加剧数据库的请求压力, 增大缓存穿透的概率
- 安全性考虑: 读写线程并发访问, 写线程删除缓存, 读线程请求缓存未命中,读线程查询数据库,读线程将结果放入缓存中, 写线程写入数据库。 这样导致了 缓存 和 数据库 数据不一致了
如果一定要选择 “先删除缓存, 在更新数据库” 这种方案, 可以采用延时双删。 为了保证第二次删除缓存的时间点在读请求更新缓存后, 这个延迟时间通常要大于业务中的读请求的耗时。
Cache-Aside 存在数据库不一致场景
- 读写线程并发访问,读线程未命中缓存, 读线程查询数据库, 写线程更新数据库, 写线程删除缓存, 读线程将结果放入缓存中。 这样导致了 缓存 和 数据库 数据不一致了
- 读写线程并发访问,写线程更新数据库,读线程命中缓存,写线程删除缓存。 这样导致了 缓存 和 数据库 数据不一致了
这种场景要求缓存失效(没有数据)并且读请求先于写请求访问, 读请求晚于写请求完成。 条件非常严格
# 补偿机制
针对 Cache-Aside 存在缓存删除失败的问题,可以采用补偿机制
删除重试机制
写线程写数据库, 写线程删除缓存, 写线程查询缓存是否为空?
- 为空: 缓存删除成功,正常返回
- 不为空: 缓存删除失败,将缓存对应的key值加入到消息队列中。 由消息队列中的消费者进行重试删除
基于数据库日志(binlog)增量解析、订阅和消费
# Read-Through
大体上和Cache-Aside类似,不同点在于 Read-Through多了一个访问控制层, 读请求只和访问控制层交互,背后缓存命中与否交由访问控制层处理。
# Write-Through
写线程更新数据库, 写线程和访问控制层交互
# Write-Behind
处理写请求, 只更新缓存,之后异步更新数据库。 通常用于秒杀场景
# Write-Around
读线程未命中的情况下,读取数据库数据,然后将数据放入到缓存中,并添加缓存过期时间
写请求仅仅修改数据
参考博客
https://cloud.tencent.com/developer/article/1932934