缓存雪崩、缓存击穿、缓存穿透
详细见:缓存雪崩、缓存击穿和缓存穿透
我这里主要是谈一谈,如何理解性记忆这三种缓存失效情况。
缓存穿透:请求的数据既不在缓存中,也不在数据库中(访问的数据压根就没有存在过),导致每次请求都会穿透缓存访问数据库,增加后端数据库的压力
缓存击穿:缓存中某些热点数据缓存失效时(过期了),多个请求同时访问这个缓存,导致多个请求同时打到数据库或后端服务上,形成“击穿”现象
缓存雪崩:缓存中的大量数据在同一时间失效,导致大量请求直接访问数据库或后端服务,给数据库带来巨大的压力,可能导致数据库崩溃

缓存与数据库一致性问题
首先需要取得以下两点共识:
- 缓存必须有过期时间
- 保证数据库与缓存的最终一致性即可,不必追求强一致性
旁路缓存
也称为手动加载缓存。读取缓存、读取数据库和更新缓存的操作都在应用系统中完成,是业务系统最常用的缓存策略。
在这种策略中,缓存的加载和维护是由应用程序负责的,而不是由缓存系统自动管理的。
读取数据流程:
- 缓存命中:应用程序需要读取数据时,先检查 Redis 缓存是否命中,如果命中就直接返回给应用程序
- 缓存未命中:如果数据不在 Redis 中(缓存未命中),那么应用程序将从数据库中获取数据
- 复制数据到 Redis:把数据库查询到的数据复制到 Redis 中,以便后续读取相同数据会命中 Redis 缓存
- 返回数据:应用程序将获取到的数据返回给调用方
写数据流程:
- 应用程序更新数据库数据
- 手动更新或删除相应的缓存项,以确保缓存与数据库保持一致
注:通常选择删除缓存,因为更新缓存的成本很高,可能需要访问多张表联合计算。
问题分析
详细见:数据库和缓存如何保持一致性?
旁路缓存存在一个问题,由于是手动管理,存在数据在数据存储中更新但缓存未及时更新的情况,导致缓存与数据存储不一致。
在使用旁路缓存策略时,对于写操作推荐先更新数据库,再删除缓存的方案。即先更新数据库中的数据,数据更新成功后,再删除缓存中的相关条目,下次读取时缓存会重新加载数据。
缓存与数据库一致性解决方案
既然如此,我们就得保证[更新数据库,再删除缓存]是原子操作,可能会发生数据库更新成功但缓存删除失败的情况。
下面介绍两种方案,归根结底就是把操作的数据行为另存一份,直到保证两个操作成功再移除,否则多次重试。
第一种:[删除缓存]重试机制
删除缓存失败,就异步发送需要删除的 key 到消息队列中。如果删除失败且未达到重试最大次数则将消息重新加入消息队列,直到删除成功。
如果超过指定重试次数,还没有成功的话,就记录到数据库,人工介入。
这个方案的缺点是,对代码入侵性比较强,需要改造原本业务的代码。
第二种:读取 MySQL binlog 异步删除 Redis 缓存
- 更新数据库。数据库会把操作信息记录在 binlog 日志中
- 使用 canal 订阅 binlog 日志获取目标数据和 key
- 缓存删除系统 获取 canal 的数据,解析目标 key,尝试删除缓存
- 如果删除失败则将消息发送到 消息队列
- 缓存删除系统重新从消息队列获取数据,再次执行删除操作

Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。