3. OpenAPI 与缓存的关系:API 网关缓存、服务端缓存、客户端缓存、HTTP 缓存头
在 OpenAPI 场景中,缓存不是单一组件,而是一个贯穿请求全路径的分层体系。每一层缓存解决不同的问题,也引入不同的一致性风险。理解各层缓存的位置、机制与适用场景,是设计缓存一致性策略的前提。
3.1 缓存分层全景
从客户端发起请求到后端服务返回响应,缓存可能出现在四个关键位置:
- 客户端缓存 — 浏览器、移动 App、网关 SDK 本地存储。
- API 网关缓存 — 网关节点(如 Kong、APISIX、Nginx)的共享或本地缓存。
- 服务端缓存 — 应用层本地缓存(Caffeine、Guava)或分布式缓存(Redis、Memcached)。
- HTTP 缓存头 — 贯穿上述各层的控制协议(Cache-Control、ETag、Last-Modified)。
核心原则:越靠近客户端的缓存,延迟收益越大,但一致性控制越难;越靠近服务端的缓存,一致性越强,但延迟收益递减。
3.2 API 网关缓存
API 网关处于南北流量的枢纽位置,是实施缓存的高性价比节点。网关缓存通常用于:
- 聚合响应缓存:将多个后端服务的响应合并后整体缓存,减少客户端等待时间。
- 静态数据缓存:配置信息、公共字典、版本号等变化频率极低的数据。
- 限流/熔断状态缓存:网关自身的状态数据,不依赖后端。
| 网关产品 | 缓存机制 | 失效方式 | 一致性等级 |
|---|---|---|---|
| Kong | Plugin (proxy-cache) | TTL + 手动清除 | 最终一致性 |
| APISIX | Plugin (proxy-cache) | TTL + Admin API 清除 | 最终一致性 |
| Nginx + Lua | lua-resty-lrucache / shared dict | TTL + Lua 脚本主动失效 | 最终一致性 / 可配置 |
| Spring Cloud Gateway | 本地缓存 + Redis | TTL + 消息广播失效 | 可达到强一致性 |
避坑提示:网关缓存默认是最终一致性。如果业务要求“写入后立即读取到最新数据”,必须在网关层禁用缓存,或使用 Cache-Control: no-cache 头绕过。
3.3 服务端缓存
服务端缓存分为本地缓存和分布式缓存两类,它们在 OpenAPI 场景中的角色差异明显:
| 维度 | 本地缓存(Caffeine / Guava) | 分布式缓存(Redis / Memcached) |
|---|---|---|
| 数据容量 | 受 JVM 堆内存限制 | 可水平扩展,容量大 |
| 访问延迟 | 纳秒级(堆内访问) | 微秒级(网络 RTT) |
| 一致性 | 单机强一致,多机需广播 | 依赖缓存策略,可配置强一致 |
| 典型场景 | 热点数据、配置、用户会话 | 共享数据、排行榜、分布式锁 |
| 失效复杂度 | 低(TTL + 手动清除) | 中(TTL + 发布订阅 + 主动删除) |
架构建议:在 OpenAPI 场景中,推荐采用本地缓存 + 分布式缓存两级架构。本地缓存扛热点,分布式缓存做共享和兜底。两级之间通过 TTL 和失效消息保持最终一致性。
3.4 客户端缓存
客户端缓存是距离用户最近的缓存层,通常由浏览器或移动 App 的 HTTP 缓存模块自动管理。在 OpenAPI 设计中,客户端缓存主要依赖 HTTP 响应头 来控制:
- Cache-Control: max-age=3600 — 告诉客户端该响应在 3600 秒内可直接使用,无需请求服务端。
- Cache-Control: no-cache — 客户端必须向服务端验证缓存是否有效(配合 ETag 使用)。
- Cache-Control: no-store — 禁止客户端缓存该响应(敏感数据场景)。
- Expires — 绝对过期时间(已被 Cache-Control 取代,但部分旧客户端仍使用)。
关键认知:客户端缓存一旦生效,服务端无法主动失效。因此,对于需要强一致性的 API(如库存、余额),必须设置
Cache-Control: no-cache 或 max-age=0,让客户端每次请求都到服务端验证。
3.5 HTTP 缓存头详解
HTTP 缓存头是连接所有缓存层的控制协议。在 OpenAPI 设计中,最核心的三个头是 Cache-Control、ETag 和 Last-Modified。
3.5.1 Cache-Control 指令
| 指令 | 含义 | 典型值 | 适用场景 |
|---|---|---|---|
public |
允许所有缓存节点(网关、CDN、客户端)缓存 | public, max-age=60 |
公开数据、静态资源 |
private |
仅允许客户端缓存,禁止网关/CDN 缓存 | private, max-age=300 |
用户个性化数据 |
no-cache |
允许缓存,但每次使用前必须向服务端验证 | no-cache |
需要强验证的 API |
no-store |
禁止任何缓存 | no-store |
敏感数据、支付接口 |
max-age |
缓存有效期的秒数 | max-age=3600 |
通用有效期控制 |
s-maxage |
仅对共享缓存(网关/CDN)生效,覆盖 max-age | s-maxage=600 |
网关与客户端差异化控制 |
must-revalidate |
过期后必须向源服务器验证,不可使用过期缓存 | must-revalidate |
强一致性要求 |
3.5.2 ETag 与条件请求
ETag 是服务端为响应内容生成的唯一标识(通常是哈希值或版本号)。客户端在后续请求中通过 If-None-Match 头携带该值,服务端比较后决定返回 304 Not Modified(缓存有效)或 200 OK(新内容)。
// 服务端响应
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "abc123xyz"
Cache-Control: no-cache
// 客户端后续请求
GET /api/v1/products/42
If-None-Match: "abc123xyz"
// 服务端响应(内容未变)
HTTP/1.1 304 Not Modified
ETag: "abc123xyz"
// 无响应体,客户端继续使用本地缓存
最佳实践:在 OpenAPI 中,对列表查询和详情查询接口优先使用
ETag + Cache-Control: no-cache 组合。这既避免了无效的响应体传输,又保证了每次请求都经过服务端验证,是性能与一致性兼顾的经典方案。
3.6 缓存头组合策略
根据 API 的一致性要求,推荐以下缓存头组合模板:
| 一致性等级 | Cache-Control | ETag | 典型 API 示例 |
|---|---|---|---|
| 强一致 | no-store |
不使用 | 订单创建、支付提交 |
| 会话一致 | no-cache |
必须使用 | 用户信息、购物车 |
| 最终一致(短 TTL) | public, max-age=60 |
可选 | 商品库存(允许秒级延迟) |
| 最终一致(长 TTL) | public, max-age=3600, s-maxage=600 |
可选 | 静态配置、字典数据 |
3.7 本章小结
- OpenAPI 缓存体系分为客户端、网关、服务端三层,外加 HTTP 缓存头作为跨层控制协议。
- API 网关缓存适合聚合响应和静态数据,默认最终一致性,需关注失效延迟。
- 服务端缓存推荐“本地 + 分布式”两级架构,本地扛热点,分布式做共享。
- 客户端缓存无法被服务端主动失效,强一致场景必须使用
no-cache或no-store。 - ETag + Cache-Control: no-cache 是兼顾性能与一致性的黄金组合,适用于大多数查询类 API。
- 缓存头策略应根据 API 的一致性等级选择,避免一刀切使用
max-age或no-store。