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,打开两个客户端并按以下顺序操作:
- A 执行 GET learn:consistency
,得到 nil;A 查询数据库,拿到 v1,但暂停缓存写入。 - B 把数据库更新到 v2 并提交,随后执行 DEL learn:consistency
。 - 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:分布式锁与限流







