Redis 进阶 04:缓存故障与数据一致性

实验基线:Redis 7.4.11,先完成环境准备。除标注为宿主机终端的代码外,Redis 命令在该课指定的 redis-cli 中执行;不复制输出结果作为命令。

学习目标#

区分三种缓存故障,识别 Cache Aside 的并发竞争,建立回源保护与失败处理策略。

原理:按触发原因分类#

缓存穿透:请求的对象在数据库也不存在,每次未命中都回源。可使用参数验证、短 TTL 空值缓存或布隆过滤器,但布隆过滤器有误判,新增对象还需更新过滤器。

缓存击穿:一个热门键到期,大量请求同时回源。可使用单实例请求合并、带超时的互斥加载、预刷新或允许短期陈旧数据。锁本身不是完整解决方案,等待者必须有超时与降级路径。

缓存雪崩:很多键同一时间失效或整个 Redis 故障,引发成批回源。可使用 TTL 抖动、多级缓存、限流、熔断与分批预热。高可用降低故障概率,却不取消故障处理需求。

实验:空值缓存#

UNLINK learn:consistency:missing learn:consistency:product
SET learn:consistency:missing '{"found":false}' EX 15
TTL learn:consistency:missing
GET learn:consistency:missing

预期 TTL 不大于 15,GET 返回明确的不存在标记。只在数据库成功查询且确实不存在时写入;超时、权限错误与解析失败不属于“不存在”。

实验:手工复现旧数据回填#

假设数据库当前值为 v1,打开两个客户端并按以下顺序操作:

  1. A 执行 GET learn:consistency,得到 nil;A 查询数据库,拿到 v1,但暂停缓存写入。
  2. B 把数据库更新到 v2 并提交,随后执行 DEL learn:consistency。
  3. A 恢复,将刚才读取的旧值写入缓存:
SET learn:consistency:product v1 EX 60
GET learn:consistency:product

预期 GET 是 v1,即使 B 使用了“先更新数据库、后删除缓存”。这是读请求持有旧结果造成的竞争窗口,TTL 让旧值最终失效,但不提供强一致。

应对策略与边界#

对允许短期陈旧的普通缓存,明确最大陈旧时间、TTL、删除重试与监控。写入成功但删除缓存失败时,应留下可重试任务,不要回滚已经提交的数据库事务并声称失败就没有发生。

更严格需求可以引入版本号、按实体协调读写、基于数据库变更日志失效或绕过缓存。版本判断必须在缓存写入处原子完成,且包含数据库与缓存之间的版本来源设计。

“延迟双删”不能消除全部竞态:延迟需要覆盖未知的读取时长,且第二次删除仍可能失败。应说明其缓解目标,不能把它当作强一致保证。

常见错误#

  • Redis 故障时所有流量直接查数据库:容易把局部故障扩散。
  • 空值永不过期:对象后来创建仍长期不可见。
  • 用单个全局锁保护所有缓存键:把不同对象串行化。
  • 只监控命中率:还应看回源并发、延迟、空值比例与失败重试积压。

练习与参考答案#

把产品缓存更新为 v2,再用 65 秒 TTL 保存;设计一个“不超过 20 个同时回源”的应用方案:

DEL learn:consistency:product
SET learn:consistency:product v2 EX 65
GET learn:consistency:product

GET 为 v2。应用方案:按进程设有界回源并发与等待队列,超时后返回受控降级;多副本部署需要计算总并发或另设全局保护。65 秒只演示抖动后的取值,随机数应由应用计算,不能把所有键都固定成 65 秒。

清理#

UNLINK learn:consistency:missing learn:consistency:product

官方参考#

过期机制 · 客户端缓存失效 · 分布式锁及限制

学习导航#

上一课:Redis 进阶 03:Pipeline、事务与 Lua · 学习目录 · 下一课:Redis 进阶 05:分布式锁与限流

Redis 进阶 04:缓存故障与数据一致性
作者:LcooYa
CC BY-NC-SA 4.0
相关文章
随机文章