HTTP 缓存与 CDN 加速:API 响应头与回源实战
API 缓存加速的关键是源站正确声明 Cache-Control、ETag 与 Vary 等响应头,再配合 CDN 的边缘缓存、缓存键优化和 stale-while-revalidate 策略,把绝大多数只读请求拦截在回源之前。
- 源站必须按接口类型显式声明缓存策略,CDN 才能正确执行
- max-age 约束浏览器、s-maxage 约束 CDN,API 场景应分开设置
- 缓存键要剔除 apikey 与时间戳等无关参数,否则命中率接近零
- 用 stale-while-revalidate 加 TTL 随机抖动避免过期瞬间回源雪崩
- 以边缘命中率、回源 QPS、304 比例和 P95/P99 延迟验证效果
引言:自动化流量时代,缓存是 API 的第一道防线#
近两年 API 的流量结构发生了明显变化:来自脚本、数据采集器、AI Agent 的调用比例越来越高。关于“为什么编程特别容易被 AI 自动化”的讨论很多,但对接口提供方来说,更直接的后果是请求模式变了——不再是少量用户手动点击,而是大量并发、高频、带自动重试的机器调用。如果源站仍按“每个请求都实时计算”的思路设计,很快就会被回源流量压垮。
常见的应对方式是加人机验证或工作量证明挑战,但这类手段成本高、且容易误伤正常用户。对于读多写少的接口,性价比最高的方案仍然是:把 HTTP 缓存语义写正确,再让 CDN(Content Delivery Network,内容分发网络) 在前端拦截掉绝大多数回源请求。本文以 API.PLUS 的公开接口为例,给出一套可以直接落地的缓存与加速配置。
一、把缓存语义写进每一个响应头#
很多接口性能差,不是因为没上 CDN(Content Delivery Network,内容分发网络),而是因为源站压根没告诉 CDN 该怎么缓存。源站必须为不同接口明确声明策略,CDN 才能正确执行。下面是几类典型接口的建议配置:
| 接口类型 | 建议 Cache-Control | 说明 |
|---|---|---|
| 公开只读查询(如 IP 查询接口) | public, max-age=30, s-maxage=120, stale-while-revalidate=60 | 同一个 IP 的归属地短期内不会变,边缘可缓存更久 |
| 随机内容(如 随机动漫壁纸接口) | no-store | 每次请求都必须拿到不同结果,缓存会破坏语义 |
| 写操作与个人数据(如 POST 示例接口) | private, no-store | 带密钥的响应绝不能进入共享缓存 |
| 静态文档、图片资源 | public, max-age=31536000, immutable | 文件名带内容哈希时可长期缓存 |
| 故障兜底 | 附加 stale-if-error=86400 | 源站短时不可用时用旧副本继续服务 |
几个容易被忽略的要点:
- max-age 与 s-maxage 要分开。前者约束浏览器,后者约束 CDN(Content Delivery Network,内容分发网络);API 场景通常希望边缘缓存久一点、浏览器缓存短一点,避免客户端拿到过期数据。
- stale-while-revalidate 是 API 的性价比利器。副本过期后仍可先返回旧数据,同时异步回源刷新,既能显著降低长尾延迟,也能在流量尖峰时保护源站。
- 可缓存响应里不要写 Set-Cookie,否则中间层可能直接放弃缓存。
验证响应头是否正确,最快的方式是直接看头部,而不只看返回体:
示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。
curl -sI 'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8'重点看 Cache-Control、ETag、Vary、Age 四个字段。Age 大于 0 说明命中了中间层缓存,这个数字本身就是最直观的命中证据。
二、缓存键与 Vary:最容易被踩的坑#
缓存键决定了“哪些请求算同一个资源”。默认情况下,大多数 CDN(Content Delivery Network,内容分发网络) 会把完整查询串纳入键,这在 API 场景里往往是灾难:
- 把
apikey放在 URL 里,等于每个用户一条独立缓存,命中率接近于零。更好的做法是通过请求头传递密钥,并在边缘把该参数从缓存键中剔除。 - 前端自动追加的时间戳
_t=、追踪参数等,会让缓存彻底失效。需要在 CDN(Content Delivery Network,内容分发网络) 侧配置参数白名单,只保留真正影响结果的字段。 - 参数顺序不稳定(
?a=1&b=2与?b=2&a=1)也会产生两个缓存键,规范化参数顺序能明显提升命中率。
关于 Vary,原则是“够用就好”:Vary: Accept-Encoding 必须保留,否则压缩与未压缩内容会互相污染;但千万不要 Vary: User-Agent,那相当于给每个客户端版本建一份副本,命中率会断崖式下跌。
对随机型接口(例如 随机动漫壁纸接口),除了标 no-store,还应确认 CDN(Content Delivery Network,内容分发网络) 没有强制开启“忽略源站缓存头”的全局规则——很多“随机接口返回同一张图”的故障,根因都是边缘层擅自缓存。
三、条件请求:用 304 省下带宽与 CPU#
对必须回源的请求,协商缓存是第二道优化。源站为响应生成 ETag(JSON 接口可基于响应体内容哈希),客户端或 CDN(Content Delivery Network,内容分发网络) 下次带上 If-None-Match,源站只需比较哈希即可返回无响应体的 304。
ETag: W/"9f2c1a7b"
Cache-Control: public, max-age=30, s-maxage=120, stale-while-revalidate=60实测中,304 能让回源带宽下降一个数量级,同时把源站序列化 JSON 的 CPU 开销省掉。注意两点:ETag 不要使用会随实例变化的值(例如进程 ID、时间戳),否则多实例部署时会互相“打架”;304 响应必须同样携带 Cache-Control 与 ETag,供边缘层刷新元数据。
四、源站与 CDN(Content Delivery Network,内容分发网络) 配合的配置示例#
推荐把缓存策略收敛在源站统一注入,避免 CDN(Content Delivery Network,内容分发网络) 默认值与业务语义冲突。下面是 Nginx 侧的配置片段:
server {
listen 443 ssl http2;
server_name _;
# 公开只读查询:允许边缘缓存,过期后异步刷新
location = /api/get {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
etag on;
add_header Cache-Control "public, max-age=30, s-maxage=120, stale-while-revalidate=60, stale-if-error=86400" always;
add_header Vary "Accept-Encoding" always;
}
# 随机内容:明确禁止任何层缓存
location = /api/dongman {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
add_header Cache-Control "no-store" always;
}
}在 CDN 控制台侧,配套打开三个开关:回源请求合并(同一缓存键的并发未命中只回源一次)、stale-while-revalidate 支持、以及参数白名单。分层策略建议为:边缘 TTL 120 秒、中间层 TTL 60 秒、浏览器 TTL 30 秒,越靠近用户副本寿命越长,越靠近源站数据越新鲜。
这里尤其要注意“缓存雪崩”的问题:如果全站统一设成同一时刻到期,大量键会同时失效并一起回源。解决办法是给 s-maxage 加随机抖动(例如 120±20 秒),或者依赖 stale-while-revalidate 把并发回源折叠成单次。
五、命中率与效果观测#
缓存配置是否生效,不靠感觉,靠指标。建议至少跟踪以下几项:
- 边缘缓存命中率:健康值通常在 80% 以上,低于 60% 说明缓存键设计有问题。
- 回源 QPS(Queries Per Second,每秒请求数) 与回源带宽:这两个数字直接对应源站成本。
- 304 比例:衡量协商缓存是否生效。
- P95 / P99(99 分位延迟) 延迟:命中与未命中分别统计,差距就是缓存的真实收益。
上线时用 5% 流量灰度切换缓存规则,对比灰度组与对照组的命中率和错误率,再逐步放量。切忌一次性把全站改成 no-cache 或 max-age=0——这会让 CDN 彻底变成一条昂贵的转发链路,源站压力反而更大。
六、总结#
在自动化客户端大量涌入的今天,缓存与 CDN 不再是可选项,而是接口稳定性的基础设施。落地时记住四条主线:源站把 Cache-Control、ETag、Vary 写正确;缓存键剥离密钥与无关参数;用 stale-while-revalidate 与抖动避免雪崩;用命中率和回源 QPS(Queries Per Second,每秒请求数) 验证效果。做到这几点,多数只读接口的回源压力可以下降一个数量级。
想直接验证本文的示例,可以在 API.PLUS 注册账号并获取免费 API Key,然后按 IP 查询接口文档 发起请求,观察响应头里的缓存字段;更多接口用法可以查看 API 文章中心,配置过程中遇到问题也可以走 技术支持 渠道反馈。