3、缓存一致性场景分析:读写缓存、缓存穿透、缓存击穿、缓存雪崩场景下的API契约需求

在分布式系统中,缓存是提升读取性能的核心手段,但缓存与数据库之间的数据不一致问题始终是治理难点。本章从四种经典缓存异常场景出发,分析每种场景对 API 契约提出的具体需求,为后续的错误处理与降级设计奠定场景基础。

3.1 读写缓存场景与一致性契约

读写缓存是最基础的缓存使用模式,其核心矛盾在于:写操作更新数据库后,缓存何时失效或同步。不同的更新策略对 API 的响应语义、幂等性、重试行为有截然不同的要求。

更新策略 一致性级别 API 契约需求 典型适用场景
Cache-Aside(旁路缓存) 最终一致性 写 API 返回后异步删除缓存;读 API 需容忍短暂脏读 用户信息、商品详情
Write-Through(穿透写) 强一致性(同事务) 写 API 必须返回缓存更新结果;失败时需回滚数据库 库存扣减、账户余额
Write-Behind(异步写) 弱一致性 写 API 仅确认缓存写入;数据库异步落盘需补偿接口 访问计数、日志聚合
契约设计要点: 在 OpenAPI 规范中,Cache-Aside 策略的写接口应在响应头中加入 X-Cache-Invalidation: true 标记,告知调用方缓存已失效;读接口应声明 Cache-Control: max-age=60 以明确容忍的过期窗口。

3.2 缓存穿透场景与契约需求

缓存穿透指请求查询一个数据库中也不存在的数据,导致每次请求都绕过缓存直达数据库。高并发下,穿透流量可能压垮后端存储。

3.2.1 穿透的典型成因

  • 恶意攻击: 遍历不存在的 ID(如负数、超大值、随机字符串)。
  • 业务漏洞: 前端未做参数校验,后端未做空值缓存。
  • 缓存过期: 热点空值在缓存中过期后,再次被大量请求。

3.2.2 API 契约的防御性需求

契约维度 具体需求 OpenAPI 示例
参数校验 对查询参数做格式、范围、枚举校验 minimum: 1, maximum: 1000000
空值缓存 接口应返回标准空值响应,并声明缓存 TTL 404 + X-Cache-Empty: true
限流熔断 对高频无效查询实施速率限制 429 + Retry-After
布隆过滤器 前置过滤接口,判断 key 是否存在 POST /bloom/check
常见误区: 仅依赖布隆过滤器而忽略 API 参数校验契约。布隆过滤器存在误判率,且无法覆盖动态生成的 key。正确的做法是:参数校验 → 布隆过滤 → 空值缓存,三层防御在契约中逐级声明。

3.3 缓存击穿场景与契约需求

缓存击穿指热点 key 在失效瞬间,大量并发请求同时穿透到数据库。与穿透不同,击穿的目标 key 在数据库中真实存在,但缓存恰好过期。

3.3.1 击穿的保护策略对比

策略 原理 API 契约影响 一致性代价
互斥锁(Mutex) 第一个请求加锁回源,其余等待 接口需声明超时时间与重试策略 低(串行回源)
逻辑过期 缓存永不过期,后台异步更新 接口需返回过期标记,调用方自行决定是否等待 高(可能读到旧数据)
分布式锁回源 Redis Redlock 或 ZooKeeper 锁 接口需携带锁标识,支持锁释放回调 中(锁竞争开销)

3.3.2 契约中的降级信号

当检测到击穿风险时,API 应通过响应头或状态码通知调用方进入降级模式:

  • X-Cache-Status: STALE — 返回的是过期缓存数据,调用方可接受降级。
  • X-RateLimit-Remaining: 0 — 触发限流,调用方应退避。
  • 503 + Retry-After: 5 — 服务暂时不可用,建议 5 秒后重试。
最佳实践: 在 OpenAPI 的 responses 中为每个读接口显式定义 503 响应,并附带 headers.Retry-After 字段。这能让客户端 SDK 自动生成退避逻辑,无需人工干预。

3.4 缓存雪崩场景与契约需求

缓存雪崩指大量缓存 key 在同一时间过期,或缓存节点整体宕机,导致所有请求直接打到数据库。雪崩是穿透和击穿的叠加态,破坏力最大。

3.4.1 雪崩的 API 契约分层防御

  1. 过期时间离散化: 在缓存写入时,对基础 TTL 增加随机偏移(如 ±10%),避免批量过期。契约中应声明 X-Cache-TTL-Base 和 X-Cache-TTL-Jitter。
  2. 本地缓存兜底: 当远程缓存不可用时,API 应返回本地进程缓存(如 Caffeine)。契约需声明 X-Cache-Local: HIT 或 MISS。
  3. 数据库限流: 在 API 网关层对数据库查询路径实施令牌桶限流。契约中通过 429 响应码和 X-DB-RateLimit 头传递限流状态。
  4. 熔断降级: 当数据库错误率超过阈值(如 50%),API 应直接返回降级响应。契约中定义 X-Circuit-Breaker: OPEN 头。
雪崩场景下的契约核心原则: 任何读接口都必须声明 降级响应模型,包括降级数据的来源(本地缓存/默认值/空对象)、降级数据的时效性(stale-while-revalidate 窗口)、以及降级后的重试策略。这些信息应集中定义在 OpenAPI 的 x-cache-policy 扩展字段中。

3.5 四种场景的契约需求总览

下表汇总了四种缓存异常场景对 API 契约的差异化需求,供架构师在设计阶段逐项检查:

场景 核心风险 契约防御手段 降级信号 一致性要求
读写缓存 数据不一致 失效标记、事务回滚 X-Cache-Invalidation 最终/强(按策略)
缓存穿透 DB 被打穿 参数校验、空值缓存、布隆过滤 404 + X-Cache-Empty 不要求(空值)
缓存击穿 热点 key 失效 互斥锁、逻辑过期、分布式锁 X-Cache-Status: STALE 弱一致性可接受
缓存雪崩 批量过期/节点宕机 TTL 离散化、本地缓存、限流熔断 503 + Retry-After 最终一致性

3.6 场景驱动的契约设计流程

下图展示了从场景识别到契约落地的完整设计流程,帮助团队在 API 设计阶段系统性地覆盖缓存异常场景:

缓存场景驱动的 API 契约设计流程 1. 场景识别 穿透/击穿/雪崩 2. 风险分析 DB 压力/一致性损失 3. 策略选择 锁/空值/本地缓存 4. 契约定义 响应码/头/扩展字段 5. 降级设计 降级数据/重试策略 6. 测试验证 混沌工程/契约测试 反馈闭环:契约不满足时返回步骤 4 输出产物 OpenAPI 规范 + 降级策略文档 + 测试用例

3.7 契约落地检查清单

在完成场景分析后,建议团队使用以下清单逐项确认 API 契约的完整性:

  • ☐ 读接口是否声明了缓存命中状态?(X-Cache-Status: HIT/MISS/STALE)
  • ☐ 写接口是否声明了缓存失效策略?(X-Cache-Invalidation: true + 失效 key 列表)
  • ☐ 是否为空值查询定义了标准响应?(404 + X-Cache-Empty: true + 空值 TTL)
  • ☐ 是否在 503 响应中声明了 Retry-After?(雪崩降级必备)
  • ☐ 是否在 429 响应中声明了限流配额?(X-RateLimit-Remaining + X-RateLimit-Reset)
  • ☐ 是否在扩展字段中定义了降级数据模型?(x-cache-policy.stale-while-revalidate)
  • ☐ 是否对热点 key 的过期时间做了离散化声明?(X-Cache-TTL-Jitter)