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 高可用架构模式
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 三大目标的关系与权衡
权衡原则:
- 一致性 vs 可用性:最终一致性允许短暂不一致,换取更高的可用性和更低的延迟。契约中应明确不一致窗口上限。
- 幂等性 vs 性能:幂等检查(如去重表、CAS)会引入额外开销。契约应允许客户端在低风险场景下跳过幂等检查(如幂等键 TTL 内可复用)。
- 容错 vs 复杂度:降级和补偿机制增加系统复杂度。契约应提供清晰的降级标识,让客户端能够做出合理决策。
4.5 契约检查清单
在定义缓存一致性接口契约时,请逐项确认以下要点:
- 一致性声明:接口文档中明确标注一致性模型(最终一致性)及最大不一致窗口。
- 版本控制:所有写接口必须支持版本号或时间戳参数,读接口必须返回版本信息。
- 幂等机制:写接口必须实现至少一种幂等策略(Idempotency-Key、CAS、唯一键)。
- 降级标识:响应中必须包含降级状态标识(如 X-Cache-Status 头或 degraded 字段)。
- 重试策略:接口必须返回 Retry-After 头或明确的重试间隔建议。
- 对账接口:提供数据对账接口,用于后台补偿任务扫描不一致数据。
- 熔断保护:网关或服务端实现熔断器,返回 503 状态码及预估恢复时间。
最佳实践:将上述检查清单写入接口契约的“非功能性需求”章节,并在 API 版本发布前进行一致性、幂等性、容错性三方面的自动化测试。