4. 缓存一致性接口契约目标

缓存一致性接口契约的核心目标,是在分布式系统不可避免的异常、并发与故障场景下,仍然保证数据状态的正确性与系统的持续可用性。本章将围绕三个核心目标展开:数据最终一致性、接口幂等性、高可用与容错,并给出对应的契约设计原则与实现策略。

4.1 数据最终一致性

在分布式缓存与数据库之间,强一致性往往意味着极高的延迟与可用性损失。因此,缓存一致性接口契约选择最终一致性作为默认一致性模型。最终一致性保证:在没有新写入的情况下,经过足够短的时间窗口,所有副本(缓存与数据库)最终会达到一致状态。

设计要点:最终一致性不是“弱一致性”,而是有明确时间边界的收敛保证。契约中应声明最大不一致窗口(如 1s、5s),并配合补偿机制确保收敛。

4.1.1 最终一致性保证层次

层次 说明 契约要求
写入收敛 写操作先更新缓存或数据库,异步同步至另一端 声明同步延迟上限(如 P99 < 500ms)
读取修复 读取时发现缓存与数据库不一致,触发修复写回 接口必须返回版本号或时间戳用于比对
后台对账 定时任务扫描不一致数据,进行批量修复 提供对账接口或数据导出能力
冲突解决 并发写入导致数据冲突时,按策略自动合并 契约中定义冲突解决策略(如 LWW、CRDT)

4.1.2 最终一致性下的接口契约约束

  • 版本号/时间戳必传:每次写入或读取操作,接口必须携带数据版本号(如 Redis 中的 CAS token)或最后修改时间戳,用于判断数据新旧。
  • 条件更新语义:写接口支持“仅在版本匹配时更新”,避免旧版本覆盖新版本。
  • 读修复触发:读接口在发现版本不一致时,自动触发修复写回,并返回修复后的数据。
// 契约示例:带版本号的条件写入
PUT /api/v1/cache/user/{id}
Content-Type: application/json

{
  "data": { "name": "张三", "age": 30 },
  "version": 1578901234,
  "ttl": 3600
}

// 响应
{
  "status": "ok",
  "version": 1578901235,
  "consistency": "eventual"
}

4.2 接口幂等性

在分布式系统中,网络超时、重试、消息重复是常态。缓存一致性接口必须保证幂等性:同一个请求无论执行一次还是多次,对系统状态的影响相同。

常见陷阱:仅靠“去重表”或“唯一键”实现幂等,在缓存场景下可能失效。例如,缓存 key 被淘汰后,重复请求可能被当作新请求处理。契约必须覆盖缓存淘汰后的幂等行为。

4.2.1 幂等性实现策略

策略 适用场景 契约要求
请求唯一 ID 写操作(新增、更新) 客户端生成 UUID,服务端去重
条件更新(CAS) 更新操作 携带版本号,仅当版本匹配时执行
幂等键(Idempotency-Key) 通用写操作 服务端缓存幂等键及响应结果,过期时间 > 重试窗口
自然幂等(SET/删除) SET 覆盖、DEL 删除 操作本身幂等,无需额外处理

4.2.2 幂等性契约设计示例

// 使用 Idempotency-Key 头实现幂等
POST /api/v1/cache/user
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "key": "user:123",
  "value": { "name": "李四" },
  "ttl": 7200
}

// 第一次请求返回 201 Created
// 相同 Idempotency-Key 的重复请求返回 200 OK(已处理)

4.3 高可用与容错

缓存一致性接口必须面对缓存节点宕机、网络分区、数据库故障等场景。契约设计需保证系统在部分故障时仍能提供降级服务,并在故障恢复后自动修复数据一致性。

4.3.1 高可用架构模式

缓存一致性高可用架构 客户端 API 网关(限流/重试) 主缓存 Redis Primary 从缓存 Redis Replica 数据库 MySQL / PostgreSQL 降级缓存(本地) 对账补偿服务 主缓存故障时,读请求降级至从缓存或本地缓存;写请求降级至数据库,由对账服务异步修复缓存

4.3.2 容错策略与契约要求

故障场景 容错策略 契约要求
缓存节点宕机 读请求降级至数据库或本地缓存;写请求直接写入数据库 接口返回降级标识(degraded: true),客户端可感知
网络分区 客户端本地缓存 + 异步重试队列 接口支持批量提交,减少网络交互次数
数据库故障 写操作暂存至缓存(带持久化),数据库恢复后回放 缓存 key 设置持久化标记,避免被淘汰
超时/重试风暴 指数退避 + 熔断器 接口返回 Retry-After 头,客户端必须遵守

4.3.3 熔断与降级契约设计

// 熔断状态响应示例
HTTP/1.1 503 Service Unavailable
Retry-After: 30
Content-Type: application/json

{
  "status": "circuit_breaker_open",
  "fallback": "local_cache",
  "estimated_recovery_seconds": 30
}

// 降级模式响应示例
HTTP/1.1 200 OK
X-Cache-Status: DEGRADED
Content-Type: application/json

{
  "data": { "name": "王五", "age": 28 },
  "source": "database",
  "consistency": "eventual",
  "degraded": true
}

4.4 三大目标的关系与权衡

缓存一致性接口契约三大目标权衡 数据最终一致性 版本号 · 读修复 · 对账 接口幂等性 唯一ID · CAS · 幂等键 高可用与容错 降级 · 熔断 · 重试 权衡 设计
权衡原则:
  • 一致性 vs 可用性:最终一致性允许短暂不一致,换取更高的可用性和更低的延迟。契约中应明确不一致窗口上限。
  • 幂等性 vs 性能:幂等检查(如去重表、CAS)会引入额外开销。契约应允许客户端在低风险场景下跳过幂等检查(如幂等键 TTL 内可复用)。
  • 容错 vs 复杂度:降级和补偿机制增加系统复杂度。契约应提供清晰的降级标识,让客户端能够做出合理决策。

4.5 契约检查清单

在定义缓存一致性接口契约时,请逐项确认以下要点:

  1. 一致性声明:接口文档中明确标注一致性模型(最终一致性)及最大不一致窗口。
  2. 版本控制:所有写接口必须支持版本号或时间戳参数,读接口必须返回版本信息。
  3. 幂等机制:写接口必须实现至少一种幂等策略(Idempotency-Key、CAS、唯一键)。
  4. 降级标识:响应中必须包含降级状态标识(如 X-Cache-Status 头或 degraded 字段)。
  5. 重试策略:接口必须返回 Retry-After 头或明确的重试间隔建议。
  6. 对账接口:提供数据对账接口,用于后台补偿任务扫描不一致数据。
  7. 熔断保护:网关或服务端实现熔断器,返回 503 状态码及预估恢复时间。
最佳实践:将上述检查清单写入接口契约的“非功能性需求”章节,并在 API 版本发布前进行一致性、幂等性、容错性三方面的自动化测试。