返回分布式系统与数据链路模块
Distributed Quality / Tutorial 18

缓存测试实战教程

从“接口返回得快”继续追问:缓存失效、热点爆发和 Redis 故障时,订单、库存与价格还能否保持正确。

9 个章节商城交易主线Redis + 一致性 + 故障
01

先画出商城缓存地图

知道缓存了什么
商城读写链路
用户请求商品详情 / 下单
应用服务读取或更新业务
Redis商品、库存、订单快照
数据库最终事实来源

缓存五类核心风险

风险商城表现常见防护
穿透查询不存在的商品,每次都落到数据库空值缓存、布隆过滤器、参数校验
击穿爆款商品缓存刚失效,大量请求同时回源互斥重建、逻辑过期、请求合并
雪崩大量 Key 同时过期或 Redis 整体不可用过期时间抖动、多级缓存、限流降级
热 Key单个商品或库存 Key 承担绝大多数流量分片、副本、本地缓存、热点识别
大 Key订单聚合对象过大,读写和删除阻塞拆分结构、渐进删除、大小门禁

先回答五个问题

  • 缓存 Key 的命名、数据结构、TTL 和容量上限是什么?
  • 读写采用 Cache Aside、Read Through 还是其他模式?
  • 数据库与缓存谁是事实来源,允许多久不一致?
  • 缓存未命中、超时或返回坏数据时走什么路径?
  • 哪些 Key 会影响金额、库存和订单状态,必须按 P0 处理?
缓存测试不是只执行 GET 和 SET。你要证明性能收益存在,同时证明缓存不会把旧价格、错误库存或过期订单状态放大给更多用户。
02

建立命中、未命中与回源基线

先测正常路径

可直接执行的基础用例

用例标题操作关键断言
当首次查询商品详情时,系统应回源数据库并写入缓存删除测试 Key;请求一次详情DB 查询 1 次;缓存写入;响应正确
当再次查询同一商品时,系统应命中缓存连续请求相同商品DB 查询不再增加;命中率提升
当更新商品价格时,用户不应长期读到旧价格更新价格后持续查询在约定 SLA 内返回新值,不回写旧值
当 Redis 不可用时,核心下单链路应按策略降级隔离环境断开 Redis不发生缓存依赖级联;告警和恢复记录完整
Redis CLI:隔离测试 Key
SET test:product:1001 '{"price":9900,"stock":10}' EX 300
TTL test:product:1001
GET test:product:1001
DEL test:product:1001

执行步骤

  1. 使用专用商品和 test: 前缀,记录数据库原值。
  2. 清空单个测试 Key,发送一次请求并记录 DB 查询、延迟和缓存内容。
  3. 再次请求,确认命中缓存且业务响应不变。
  4. 等待 TTL 或主动失效,确认能正确回源并重建。
  5. 结束后只删除本次创建的测试 Key。
03

阻止不存在的数据持续穿透

保护数据库

用例:当并发查询不存在的商品时,数据库不应被重复击穿

  • 构造一个永不存在的商品 ID,并发请求 100 次。
  • 确认参数校验、布隆过滤器或空值缓存生效。
  • 统计应用请求数、缓存未命中数与数据库查询数。
  • 验证空值 TTL 较短,商品随后创建时不会长期不可见。
k6 场景示意
export default function () {
  const id = "NOT-EXIST-" + (__VU % 5);
  http.get(BASE_URL + "/api/products/" + id);
}
// 断言:100 次请求不应对应 100 次数据库查询
只看 HTTP 404 会漏掉真正风险:返回完全正确,但每次 404 都查一次数据库,攻击流量仍可能拖垮系统。
04

验证爆款缓存击穿防护

一个 Key 同时过期

爆款商品失效时的正确行为

T0

热点 Key 过期

T1

一个请求获得重建权

T2

其他请求等待或读逻辑旧值

T3

新值写入后恢复命中

用例:当爆款 Key 在高并发下失效时,只应触发受控回源

  • 预热爆款商品并把 TTL 调到可控短时间。
  • 在过期点同时发起高并发读取。
  • 断言数据库查询数被限制,锁有超时且不会死锁。
  • 重建失败时旧值、错误响应或降级结果符合策略。
  • 重建后所有请求最终读取同一版本。
05

让缓存雪崩成为可控退化

大量 Key 同时失效

雪崩演练矩阵

注入观察通过标准
同批 1000 个商品 Key 同时过期DB QPS、连接池、P99不过载;限流生效;恢复后无持续积压
Redis 延迟增加 300ms线程池、超时、重试重试有上限,不形成重试风暴
一个节点不可用错误率、故障转移核心读请求按策略恢复或降级
缓存命中率骤降告警与回源比例告警及时且能定位到 Key 空间

预防性检查

  • TTL 是否加入合理随机抖动,而不是同一秒批量过期。
  • 缓存超时是否短于下游总超时,重试是否带退避。
  • 数据库是否有回源限流和熔断保护。
  • 降级数据是否明确标识,支付价格不可用旧值静默结算。
06

识别热 Key 与大 Key

不让单点拖慢集群

热 Key 测试

  • 按 Key 统计访问频率和节点负载。
  • 模拟爆款和普通商品的流量倾斜。
  • 验证副本、本地缓存或拆分策略。
  • 检查热点消失后资源能否恢复。

大 Key 测试

  • 测量序列化大小、元素数量和操作耗时。
  • 验证读取、更新、过期和删除的尾延迟。
  • 检查拆分与渐进删除是否生效。
  • 设置超过阈值的写入门禁和告警。
只读检查示意
redis-cli --bigkeys
redis-cli --hotkeys
MEMORY USAGE test:order:aggregate:1001
# 仅在隔离测试环境使用诊断命令
07

验证数据库与缓存一致性

连接上一章的数据链路
订单取消后的更新路径
取消订单写数据库 CANCELLED
提交事务发送失效事件
删除缓存避免继续读旧值
再次查询回源并写入新状态

用例:当订单取消与查询并发发生时,不应把旧状态覆盖回缓存

  1. 准备 PAID 订单并预热缓存。
  2. 暂停一次旧状态查询,在数据库提交 CANCELLED。
  3. 触发缓存删除,再释放旧查询继续执行。
  4. 断言旧值不会覆盖新值;最终页面、API、缓存和数据库均为 CANCELLED。
  5. 重复取消,确认退款和库存释放仍只有一次。
一致性测试必须同时检查“最终读到新值”和“没有重复副作用”。前者保护展示,后者保护库存与资金。
08

演练 Redis 超时、断连与恢复

故障不等于删除数据

安全演练步骤

  1. 只在隔离环境声明故障范围、观察窗口和终止条件。
  2. 记录基线命中率、订单成功率、DB QPS 与 P99。
  3. 注入延迟、连接失败或单节点故障,不执行 FLUSHALL。
  4. 验证超时、熔断、限流、降级、告警与故障转移。
  5. 撤销故障,确认连接池、命中率和业务指标恢复且数据一致。

恢复验收

对象必须证明
商品读取可降级但不会把错误价格用于结算
库存扣减不能只改缓存;数据库事实与防超卖规则仍有效
订单查询短暂失败可解释,恢复后不长期返回旧状态
监控告警故障开始、影响、恢复都有时间线
09

完成一次缓存测试小项目

练习与检查清单

练习

  1. 为商品、库存和订单画出 Key、TTL、读写模式与事实来源。
  2. 执行命中、未命中、过期、更新和删除的基线用例。
  3. 分别设计一条穿透、击穿、雪崩、热 Key 和大 Key 用例。
  4. 模拟订单取消与缓存回填竞态,保留完整时间线。
  5. 注入 Redis 延迟,输出业务影响、告警、降级与恢复报告。

设计前

  • Key 与 TTL 明确
  • 一致性 SLA 明确
  • 测试数据隔离
  • 故障边界批准

执行中

  • 业务与技术指标齐全
  • 并发与竞态已覆盖
  • 不使用危险清库命令
  • 证据带订单与 traceId

完成后

  • 缓存与数据库一致
  • 资源恢复基线
  • 测试 Key 已清理
  • 高风险场景进入回归