你的接口在裸奔:HTTP缓存这盘棋,多少人下得一塌糊涂

2026-05-21 11 0

事情是这样的。

上周线上出事了,商品列表接口被人狂刷,QPS瞬间飙到三千多,数据库直接升天。监控报警响了一晚上,最后排查出来的原因让我一口老血喷出来——后端哥们儿把HTTP缓存头全注释掉了,理由是「怕线上数据不一致」。

我:???

怕数据不一致就把缓存全关掉,这逻辑就好比「怕出车祸所以把车砸了走路」。HTTP缓存是CDN的命根子,是后端扛并发的神器,你一句话就给阉了,CDN白买了吗?

所以今天来好好聊聊HTTP缓存,这玩意儿水很深,懂的人能把它玩出花,不懂的人只会Cache-Control: no-cache来回配置。

先搞清楚你是在跟谁对话

HTTP缓存不是铁板一块,它分两层:私有缓存共享缓存

浏览器缓存就是私有缓存,只属于你自己的浏览器,谁都碰不着。CDN、代理缓存就是共享缓存,一堆人共用一份资源。

这两个玩的东西完全不一样。很多新手把私有缓存的规则套到共享缓存上,结果CDN缓存失效,源站被打成筛子。

私有缓存:Cache-Control: private
共享缓存:Cache-Control: public

记住这句话:只有当数据足够「公开」,且对一致性要求不那么变态的时候,才值得上CDN缓存。用户个人数据就别想了,老老实实走私有缓存或者不放缓存。

强缓存和协商缓存,别再傻傻分不清

这是最让人晕的两个概念,我用大白话解释一下。

强缓存:浏览器看都不问服务器,直接从本地缓存里拿。服务器根本不知道你访问了。
协商缓存:浏览器先问问服务器,这东西有没有新版本?有就拿新的,没有就拿本地缓存的。

强缓存的响应头是这两个:

Cache-Control: max-age=3600
Expires: Thu, 21 May 2026 10:00:00 GMT

协商缓存靠这两个:

Last-Modified: Thu, 21 May 2026 08:00:00 GMT
ETag: "abc123"

重点来了——ETag是骨灰级玩家用的东西。Last-Modified只能精确到秒,遇到一秒内修改多次的情况就傻眼了。ETag是服务器根据文件内容生成的哈希值,内容变了他就变,精确到字节级。

但ETag有代价:每次请求都要去服务器验证。对于大文件或者高并发场景,这玩意儿可能比不用缓存还慢。所以Facebook这种量级的公司,早就抛弃了ETag,全靠Cache-Control撑场面。

Cache-Control不是一个值,是一整套指令

很多人以为Cache-Control就一个max-age,其实它是一个组合拳:

Cache-Control: public, max-age=31536000, s-maxage=604800, must-revalidate

让我拆解一下:

  • public:响应可以被任何缓存存储,包括CDN和代理服务器
  • max-age=31536000:浏览器缓存有效期一年(相对时间)
  • s-maxage=604800:CDN缓存有效期七天(只对共享缓存生效,忽略max-age)
  • must-revalidate:缓存过期后必须去服务器验证,不能拿过期的当最新用

看到没?CDN和浏览器可以设置不同的缓存时间!这是很多人不知道的骚操作。

一般套路是:源站缓存短,CDN缓存长。比如:

# 源站配置(nginx)
location /static/ {
    expires 7d;          # 浏览器缓存7天
    add_header Cache-Control "public, s-maxage=2592000";  # CDN缓存30天
}

为什么要这样?因为静态资源更新的时候,你需要能及时推倒CDN的旧缓存。如果CDN缓存时间和浏览器一样长,那上线后用户得等一个月才能看到新版本,这显然不行。

Vary头——这个坑踩一次疼一周

如果你做过SSR或者API网关,Vary头你一定要懂。

假设你的API返回跟语言相关的文案:/api/products?locale=zh/api/products?locale=en。如果你在CDN上缓存了中文版,然后有个英文用户来访问,CDN直接把中文结果返回给他——因为URL不一样,缓存key就不一样……不对,URL不一样,缓存key本来就不一样啊?

等等,如果CDN忽略查询参数呢?

# nginx配置忽略查询参数做缓存
proxy_cache_key "$host$uri";  # 默认不带$query_string

这种情况你就需要Vary头来救命:

Vary: Accept-Language, Accept-Encoding

这告诉缓存:缓存的时候要把这些请求头也考虑进去。同样是/api/products,中文浏览器和英文浏览器拿到的是两份不同的缓存。

这个坑我真踩过,当时有个接口返回的内容跟用户所在地区有关,上CDN后有用户反馈看到的是别人的内容,排查了大半天才发现是Vary没配。差点被祭天。

不要缓存的几种正确姿势

很多人以为「不缓存」就是Cache-Control: no-cache,nonono。

HTTP规范定义了一堆指令,用错场景你会死得很惨:

# 不缓存,但每次都去服务器验证(协商缓存)
Cache-Control: no-cache

# 完全不存储缓存,包括CDN和浏览器
Cache-Control: no-store

# 缓存但不使用,强制每次都去源站(HTTP/1.0兼容)
Pragma: no-cache

生产环境用得最多的是no-store,特别是涉及金钱、用户隐私、或者实时性要求极高的数据。如果你用了CDN,敏感数据一定要加no-store,不然CDN节点可能把你的数据泄露给别的用户。

至于no-cache,很多人误解成「不缓存」,结果配置了CDN还是被缓存了。实际上no-cache的意思是「缓存但每次验证」,CDN还是会存的。

一个实战场景:如何设计一个「可缓存但又不能太久」的API

这是最常见的需求:数据可以缓存,但不能超过一定时间,因为业务逻辑会变。

标准做法是用max-age+stale-while-revalidate

Cache-Control: public, max-age=60, stale-while-revalidate=30

意思是:缓存60秒内直接用缓存,不用问服务器;60秒后120秒内,用户会拿到缓存数据,同时浏览器在后台发起验证请求更新缓存。

这样做的好处是什么?用户体验起飞——大部分用户拿到的都是缓存,没有网络延迟。但缓存不会太旧,最多「过期」了30秒业务就更新了。

这个头部在GitHub API和Cloudflare Workers里用得很多,是性能和一致性之间的完美平衡点。

最后说一句

HTTP缓存这个话题,说深了可以写一本书。但很多后端同学连Cache-Control都配不明白,更别说理解CDN和浏览器各自的行为差异了。

我见过把用户个人信息设成public缓存导致数据泄露的;也见过把静态资源设成no-cache让CDN形同虚设的;更见过Vary头忘配导致用户看到乱码内容的。

缓存不是洪水猛兽,它是性能优化的核武器。用对了,扛十万并发不用加一台服务器;用错了,直接把你的用户数据送到别人屏幕上。

下次有人跟你说「怕数据不一致所以把缓存关了」,你可以把这篇文章甩他脸上。

……开玩笑的,好好沟通,团队和谐最重要。

但真的,这东西值得每个后端工程师深入理解。

相关文章

还在为部署AI自动化工具掉头发?我帮你搞定,39块起
你的API设计正在偷偷毁掉你的后端同学
我把五大AI塞进同一个角色扮演场景,结果笑死我了
我把五大AI塞进同一个角色扮演场景,结果笑死我了
你以为代码慢是语言问题?不好意思,可能是你自己写的烂
你的API慢,可能不是代码的问题——而是你的TCP连接在互相伤害

发布评论