用户登录

技术文章

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

HTTP 缓存与 CDN 加速实践:让命中率可测量

一句话回答

HTTP 缓存与 CDN 加速的关键不是打开开关,而是用明确的 Cache-Control、收敛的缓存键和可复现的头部验证脚本,把命中率变成可测量、可回归的工程指标。

核心要点 TL;DR
  • 缓存失效多因响应头自相矛盾,而非 CDN 配置错误
  • 静态、半静态、私有数据必须用三套不同的缓存头
  • 用 curl 连续请求观察 Age 与缓存状态头验证命中
  • 缓存键要规范化参数,避免随机 query 打散命中率

HTTP 缓存与 CDN(Content Delivery Network,内容分发网络) 加速能否真正提速,取决于响应头是否明确、缓存键是否收敛、命中率是否可测量,而不取决于是否打开了 CDN 开关。

很多团队在接入 CDN(Content Delivery Network,内容分发网络) 后会发现一个尴尬现象:带宽账单没降,源站 QPS(Queries Per Second,每秒请求数) 也没降多少。问题往往不在 CDN 厂商,而在源站返回的响应头自相矛盾——一边写 Cache-Control: public, max-age=300,一边又带 Set-Cookie,边缘节点只能选择不缓存。

为什么 API 的 HTTP 缓存经常“配了等于没配”?#

缓存不生效,九成是响应头冲突或缓存键被打散。以下是接入 API.PLUS 接口时最常见的四类失真点:

  • Set-Cookie 污染:响应里出现 Set-Cookie,多数 CDN(Content Delivery Network,内容分发网络) 默认直接跳过缓存,无论 Cache-Control 写得多激进。
  • Vary 头过宽:写了 Vary: * 或把 User-Agent、Cookie 放进 Vary,缓存键瞬间爆炸,命中率趋近于零。
  • 参数随机化:前端每次请求都带 ?_t=时间戳 或未排序的参数,同一份数据被拆成无数个缓存键。
  • 状态码不参与缓存:只给 200 配了 TTL,404 与 5xx 也进入缓存或都不进入缓存,导致错误被放大或抖动被放大。

以 随机动漫壁纸接口 为例,它返回的是图片地址与元数据,天然适合边缘缓存;而 GET 示例接口 返回的 IP 归属信息属于半静态数据,缓存策略就应该完全不同。

如何设计一套能真正命中的 API 缓存响应头?#

同一套服务里,静态、半静态、私有数据必须用三套不同的头,混用是命中率杀手。参考下面的分层策略:

数据类型Cache-ControlCDN(Content Delivery Network,内容分发网络) 侧行为典型接口
静态资源 / 不可变结果public, max-age=31536000, immutable长 TTL,靠文件名或参数版本更新动漫壁纸图片地址
半静态数据public, max-age=60, s-maxage=300, stale-while-revalidate=60边缘缓存 5 分钟,回源时先返回旧值IP 归属查询
个性化 / 鉴权响应private, no-store不进共享缓存账户余额、密钥信息
写操作no-store永不缓存POST 示例接口

s-maxage 只对共享缓存生效,浏览器会忽略它。这个特性正好用来让 CDN(Content Delivery Network,内容分发网络) 扛住更多流量,同时保证用户端拿到相对新鲜的数据。设计流程建议按四步走:

  1. 按“数据变更频率”给每个接口打标签,而不是按业务模块打标签。
  2. 为每类标签定义唯一的 Cache-Control 模板,禁止开发者在业务代码里随手写字符串。
  3. 在网关层统一剥离 Set-Cookie,确需下发的走独立响应头或前端二次请求。
  4. 为 4xx 与 5xx 单独配置短 TTL,避免错误页被长时间缓存。

CDN(Content Delivery Network,内容分发网络) 缓存键与 Vary:哪些变量必须参与?#

缓存键应只包含真正影响响应内容的变量,多一个字段就少一截命中率。推荐做法是把查询参数按字母序规范化后再参与键计算。

  • 必须参与:路径、影响内容的查询参数(如 type=json)、语言或区域标记(若确实影响内容)。
  • 不应参与:请求时间戳、随机数、埋点 ID、无关的追踪参数。
  • 谨慎参与:Origin、Accept,除非接口真的做内容协商。

如果要在 Nginx 侧自己做一层缓存,可以这样写:

示例说明:以下代码仅用于逻辑验证,生产环境需补充异常处理、超时与重试、日志与监控告警及降级策略。

proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:64m max_size=4g inactive=30m;

server {
    listen 443 ssl;
    server_name www.api.plus;

    location = /api/dongman {
        proxy_pass http://127.0.0.1:9000;
        proxy_cache api_cache;
        proxy_cache_key "$scheme$request_method$host$uri$is_args$args";
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502;
        proxy_cache_lock on;

        add_header X-Cache-Status $upstream_cache_status always;
        add_header Cache-Control "public, max-age=300, stale-while-revalidate=60" always;
    }
}

注意 add_header 会覆盖上游同名头,所以这类配置要放在网关层统一管理,避免源站与网关各写一套互相打架。proxy_cache_lock 能在缓存失效瞬间把并发回源合并成一次,对短时高频抓取尤其有效。

怎么验证 CDN 命中率是真实提升而不是错觉?#

验证只需要一个循环脚本,观察 Age 是否递增、缓存状态头是否从 MISS 转 HIT:

#!/usr/bin/env bash
URL="https://www.api.plus/api/dongman?type=json"

for i in 1 2 3 4 5; do
  echo "--- 第 $i 次请求 ---"
  curl -sS -o /dev/null -D - "$URL" -H 'Accept-Encoding: gzip' \
    | grep -Ei '^(HTTP/|cache-control:|age:|cf-cache-status:|x-cache-status:|vary:)'
  sleep 2
done

预期结果是:第一次 MISS 且 Age 缺失或为 0,后续请求出现 HIT 且 Age 逐步增长。若五次全是 MISS,按下面的顺序排查:

  1. 检查响应里是否有 Set-Cookie 或 Vary: *。
  2. 确认 CDN 是否对该内容类型启用了缓存规则,部分平台默认不缓存 JSON。
  3. 核对缓存键,把请求 URL 的完整 query 打出来比对。
  4. 查看 CDN 的缓存命中率面板,与源站访问日志的请求数交叉验证。

缓存层同时是一道廉价的流量缓冲。Cloudflare 在 2026 年第三季度 DDoS 威胁报告 中指出,攻击正转向更短、更高频的脉冲式打击。这类流量如果全部穿透到 PHP-FPM,源站几乎必然被打满;而把可缓存的读接口挡在边缘,能显著降低穿透比例。

如何用 AI Agent 流程守住缓存契约不回归?#

缓存配置最容易在迭代中悄悄退化:某次改动加了一个 Set-Cookie,命中率就从 80% 掉到 5%。可以把缓存契约当成接口契约的一部分,交给 Agent 辅助维护:

  1. 先定契约:把每个接口应有的 Cache-Control、Vary、状态码 TTL 写成一份 YAML 或表格,作为唯一事实来源。
  2. 再让 Agent 生成断言:把契约和路由文件一起交给 Agent,要求生成只断言响应头、不断言响应体的测试用例,避免测试脆弱。
  3. 人工只审 diff:重点看 Agent 是否新增了未在契约中登记的缓存行为,而不是逐行读生成代码。
  4. 进 CI:把断言挂到每次发布前的集成测试里,失败即阻断,缓存回归就变成可拦截的问题。

这套流程的价值在于把“我知道应该加缓存头”变成“机器每次都会检查缓存头”。Agent 负责体力活,人负责判断哪些数据可以被共享缓存。

总结#

HTTP 缓存与 CDN 加速的落地顺序应该是:先按数据变更频率分类,再统一响应头模板,然后收敛缓存键,最后用脚本和 CI 把命中率固定下来。顺序颠倒,就只能在面板上看到一个好看但无意义的数字。

动手验证时,可以直接用 GET 示例接口 与 随机动漫壁纸接口 跑上面的脚本,观察 Age 与缓存状态头的变化;更多接入细节可在 API 文章中心 查阅,遇到问题可通过 技术支持 反馈。在 API.PLUS(沐小悠API)注册即可获取免费 API Key,用真实请求把缓存策略跑通,比读十篇配置文档更有效。

常见问题(FAQ)

API 返回的 JSON 能不能设成长期缓存?
只有内容确实不变时才可以,例如固定 ID 的图片元数据,可设 public, max-age=31536000, immutable。会变的数据用 max-age=60 配 s-maxage=300,让 CDN 多扛一层。
CDN 显示 HIT,为什么客户端还是拿到旧数据?
通常是浏览器本地缓存仍在生效,或响应里带了较长的 max-age。排查时先看 Age 与 Cache-Control,再用 curl 加随机参数绕过本地缓存,判断是边缘节点还是浏览器在返回旧内容。
Vary 头应该写哪些字段?
一般只写 Accept-Encoding。写 Vary: * 会让所有请求都无法缓存;把 User-Agent 或 Cookie 放进 Vary 会让缓存键爆炸,命中率接近零。

作者:沐小悠

本文约 10 分钟读完 · 关键词:HTTP 缓存、CDN 加速、Cache-Control、缓存命中率、API 响应头

更多文章 GET 示例接口

「」标签下的其他文章