你有没有遇到过这种窒息时刻:接口响应时间2秒,用户截图发群里,老板的眼神比502胶水还粘人。
你开始加机器、加Redis、加CDN,折腾一圈下来——还是2秒。
然后你绝望地发现,问题可能根本不在服务器,而在你根本不懂HTTP缓存。
别急着划走。这篇文章不教你背协议文档,而是用实战告诉你,怎么让API飞起来。
先搞清楚:缓存到底是个什么东西
很多人对缓存的理解就是"把数据放内存里"。这个理解不能说错,只能说——Too young,Too simple,Sometimes naive。
HTTP缓存的本质是:让服务器少干活,让客户端自己解决问题。你每发一次同样的请求,服务器都得重新查库、组装数据、返回响应。但实际上,这个响应可能3小时内都不会变。你让服务器白干了3小时。
HTTP缓存协议设计了两种机制:
- 强制缓存(Cache-Control):客户端直接用本地缓存,不问服务器
- 协商缓存(Last-Modified / ETag):客户端问一下服务器,这个东西还能用吗
搞清楚这两个东西怎么配合,比你加十台机器管用。
Cache-Control:这是你必须搞懂的第一个头部
Cache-Control是HTTP缓存的核心担当。它不是一个值,是一堆指令的组合。常见的有:
- max-age=秒数:缓存有效期,过了这个时间才需要重新请求
- no-cache:不用本地缓存?不对,翻译错了——意思是每次都去服务器验证,别自作主张
- no-store:这个才是真正禁用缓存,告诉浏览器连存都不要存
- public:响应可以被任何缓存节点缓存(CDN、代理等)
- private:只有客户端本地能缓存,CDN不行
最容易被搞混的是 no-cache 和 no-store。面试十个后端,九个答反。你品,你细品。
实战建议:
Cache-Control: max-age=300, public // 数据5分钟内有效,可以进CDN
Cache-Control: no-cache, must-revalidate // 每次用,但要确认新鲜度
Cache-Control: private // 私人数据,别让CDN缓存!
ETag:省流量的好东西
很多人在Etag和Last-Modified之间纠结。我的建议是:两个都上,不吃亏。
Last-Modified记录文件修改时间,精确到秒。Etag是你自定义的版本号,精确到内容hash。
ETag的工作流程是这样的:
客户端 GET /api/user/123
服务端 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Content-Type: application/json
响应体 {...}
客户端(再次请求)GET /api/user/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
服务端检查:
- If-None-Match匹配 → 304 Not Modified(不返回body,只头部)
- 不匹配 → 200 OK + 新ETag + 新body
304响应有多爽?你的body可能是50KB,304就只有头部,大概几十个字节。一次省50KB,一天算你10万次请求——省了5GB流量。
这就是为什么有些接口你感觉"没改什么但变快了"——因为它根本没返回body。
实战场景:哪些地方最应该加缓存
不是所有接口都值得缓存。加错了,轻则数据过期,重则用户看到别人的隐私。
适合缓存:
- 业务配置、字典表(几乎不变)
- 公开的列表数据(新闻列表、商品列表)
- 用户头像、静态资源
- 聚合查询结果(需要设较短max-age)
不适合缓存:
- 包含用户个人数据的响应(private场景)
- 实时数据(如股价、库存)——这个过期了会出大事
- POST、PUT、DELETE等写操作的响应
- 认证相关接口(token频繁变化)
还有一个极其容易被忽略的地方:JSONP和跨域请求的缓存。很多项目用了Corsisc这类中间件,默认把缓存头全清了。做完了发现所有接口都没缓存,一看配置——哦豁。
我见过最离谱的几个缓存反模式
反模式一:max-age=0 当作禁用缓存
有人以为 Cache-Control: max-age=0 就是禁用缓存。错!浏览器会在0秒后立刻验证——但它还是会缓存的!只是每次都去校验。
如果你想真的不缓存,正确答案是:
Cache-Control: no-store, no-cache, must-revalidate
反模式二:把用户私有数据设成public
我见过有工程师把所有接口设成 public,理由是"方便CDN缓存提速"。
你这是把用户A的订单数据缓存进CDN,然后用户B可能访问到。你想上社会新闻吗?
原则:任何涉及个人隐私的数据,必须 private 或禁止缓存。这条写进团队规范里都不为过。
反模式三:只在后端做缓存,前端乱来
后端设了一堆Cache-Control,结果前端发请求时加了个 ?t=时间戳,每次URL都变,缓存全部失效。
这属于——前端自己废了自己的武功,还回头怪后端慢。团队内部联调时,这个问题出现的频率高得离谱。
怎么验证你的缓存到底有没有生效
很多人设了一堆缓存头,但根本不知道怎么验证。工具很简单:Chrome开发者工具。
打开 Network 面板,勾选 Disable cache 关掉浏览器缓存做对比,然后:
- 看 Size 列:显示 from cache 就是命中了本地缓存
- 看 Status 列:304 就是服务端缓存验证生效
- 右键点击请求 → Block request domain 可以模拟缓存过期场景
另外推荐一个命令:
curl -I https://你的接口地址
# 查看返回的头部信息,重点看:
# Cache-Control
# ETag
# Last-Modified
# Expires
很多线上问题,是从这个命令发现 ETags 缺失开始的。
一句话总结
HTTP缓存不是什么高深的技术,但它真的被严重低估了。你花两周开发的接口功能,可能因为一个 Cache-Control 设置错误,变成一个每次都打服务器的废物。
把缓存玩明白,比你买十台服务器便宜多了。
下次老板问你为什么接口慢,你就说——因为缓存没设对。
然后优雅地打开这篇文章。