用户登录

技术文章

外部链接将在新标签页打开。

HTTP 缓存与 CDN 加速:API 响应头与回源实战

一句话回答

API 缓存加速的关键是源站正确声明 Cache-Control、ETag 与 Vary 等响应头,再配合 CDN 的边缘缓存、缓存键优化和 stale-while-revalidate 策略,把绝大多数只读请求拦截在回源之前。

核心要点 TL;DR
  • 源站必须按接口类型显式声明缓存策略,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 文章中心,配置过程中遇到问题也可以走 技术支持 渠道反馈。

常见问题(FAQ)

API 接口应该怎么设置 Cache-Control?
公开只读查询用 public, max-age=30, s-maxage=120, stale-while-revalidate=60;随机内容用 no-store;写操作和个人数据用 private, no-store;故障兜底可加 stale-if-error=86400。
max-age 和 s-maxage 有什么区别?
max-age 约束浏览器缓存,s-maxage 约束 CDN 等共享缓存。API 场景通常希望边缘缓存更久、浏览器缓存更短,以避免客户端拿到过期数据,因此两者要分开设置。
apikey 放在 URL 里为什么会导致缓存命中率极低?
因为多数 CDN 默认把完整查询串纳入缓存键,apikey 不同就等于每个用户一条独立缓存。建议改用请求头传密钥,并在边缘把该参数从缓存键中剔除。
Vary 头应该怎么配置?
原则是够用就好:必须保留 Vary: Accept-Encoding,避免压缩与未压缩内容互相污染;但不要 Vary: User-Agent,否则相当于每个客户端版本一份副本,命中率会断崖式下跌。
如何判断缓存是否真的生效?
用 curl -sI 查看响应头中的 Cache-Control、ETag、Vary、Age 字段,Age 大于 0 即说明命中了中间层缓存;同时跟踪边缘命中率(健康值 80% 以上)与回源 QPS。

作者:沐小悠

本文约 10 分钟读完 · 关键词:HTTP 缓存、Cache-Control、CDN 加速、边缘缓存、API 性能优化

更多文章 GET 示例接口

「」标签下的其他文章