你的接口慢成狗,可能只是因为缓存没整明白

2026-08-12 13 0

你的接口慢成狗,可能只是因为缓存没整明白

见过太多这样的场景:系统一上线,后端同学对着监控大屏发呆——CPU不高、数据库正常、Redis也活着,但接口响应就是慢得像在等公交车。

然后一顿操作猛如虎:加机器、加索引、Redis集群……结果呢?该慢还是慢。为啥?因为方向就没整对。

今天说个被严重低估的问题——HTTP缓存。这东西说实话,很多工作三年的后端都不一定整明白了。


先搞清楚一个核心问题:谁的缓存?

HTTP缓存分两层:客户端缓存(浏览器、App)和中间层缓存(CDN、代理服务器)。很多开发者只知道后端要加缓存,却忽略了HTTP协议本身给你留的那些机制。

这两层配合好了,能把90%的请求直接拦截在服务端之外。加机器都解决不了的问题,缓存能。


Cache-Control头:你可能一直在瞎写

大多数后端同学返回的Cache-Control头,基本都是复制粘贴来的——或者直接返回个no-cache图省事。

来,看看你有没有踩过这些坑:

// 坑1:所有接口都no-cache,浏览器每次都去验证,浪费
res.setHeader('Cache-Control', 'no-cache');

// 坑2:max-age设成0,等于没缓存,每次都重新拉
res.setHeader('Cache-Control', 'max-age=0');

// 坑3:只设了public,却没考虑私有数据泄露问题
res.setHeader('Cache-Control', 'public, max-age=3600');

Cache-Control其实是有一套组合拳的:

  • max-age=3600:告诉浏览器,这个响应可以缓存3600秒,期间直接用,不来问我
  • no-cache:每次用之前都得问我(服务器),但不一定重新下载
  • no-store:彻底不要缓存,真·每次都拉新的
  • private vs public:private只能存在用户本地,public可以存在CDN等中间节点

这里最容易搞混的是no-cacheno-storeno-store是真的不存任何缓存副本,适合处理敏感数据。no-cache是存了,但每次得跟服务器确认一下——这对于需要实时数据的场景是合理的。


ETag和Last-Modified:你根本没用上的好东西

有的接口内容变一次,整个资源就变了。这时候用ETag或者Last-Modified就非常合适。

ETag本质上是一个版本标识符——服务器给资源打一个tag,变了就换tag。浏览器再次请求时,把这个tag带过去(If-None-Match头),服务器一看——哦,tag没变,返回304 Not Modified,浏览器继续用本地缓存。

// 服务器端示例(Node.js/Express风格)
app.get('/api/config', (req, res) => {
  const data = getConfigData();
  const etag = generateEtag(data); // 用数据内容的hash生成ETag
  
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end(); // 没变化,告诉浏览器用缓存
  }
  
  res.setHeader('ETag', etag);
  res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
  res.json(data);
});

304响应体是空的,网络传输量几乎为零。这比每次都返回完整的JSON要强太多。

很多后端根本不返回ETag,白白浪费了这个机制。有的甚至每次都生成新的ETag——那跟没缓存有什么区别?


CDN缓存:很多人第一反应就是"清理缓存真麻烦"

CDN缓存确实是把双刃剑。用好了能把接口响应时间从200ms压到5ms,用不好就是用户看到过期数据的噩梦。

我见过有人因为害怕缓存问题,干脆把CDN关了——这是因噎废食。

正确姿势是分层处理:

  • 静态资源(JS/CSS/图片):CDN缓存一年都行,用文件名hash来控制版本
  • 用户无关的公共数据(配置、字典表):CDN缓存, TTL设置合理范围
  • 用户私有数据:绝对不要上CDN,用private缓存
  • 实时性要求高的数据:用swr或请求合并,不要硬刚缓存

至于清理问题——你都有版本控制工具了,为啥不把缓存也做成有版本的概念?URL加个版本参数,或者用文件名hash,都能优雅解决。


一个真实的反例:那个被缓存坑死的活动页

说个我见过的真实案例。某电商平台做秒杀活动,商品信息接口设置了CDN缓存TTL=300秒(5分钟)。

结果活动一开始,商品库存从100变成了0,但用户刷了5分钟页面,看到的还是"有货"。

后端的锅吗?不一定。是缓存策略设计的时候就埋下的雷——库存这种高频变更的实时数据,就不应该用CDN做长时间缓存。

正确的做法是:库存接口走私有缓存(或者no-cache),或者前端轮询+请求合并,把实时性和性能做个平衡。


强缓存 vs 协商缓存:什么时候用哪个?

很多人搞不清楚这两个概念的区别。

强缓存:本地直接用,不问服务器。Cache-Control的max-age和Expires头就是干这个的。

协商缓存:每次用之前问一下服务器,服务器说能用就用,不能用就下载新的。ETag/Last-Modified + If-None-Match/If-Modified-Since这套组合。

选哪个?看数据更新频率:

  • 几乎不变的数据(logo、静态配置):强缓存,max-age设长一点
  • 经常变化但不要求实时(排行榜、日榜):强缓存+版本号,或者短TTL的强缓存
  • 必须实时(订单状态、库存):协商缓存或者不缓存

写在最后

HTTP缓存不是玄学,也不是加个Redis就完事了的事。它是HTTP协议层面就定义好的一套机制,用好了能四两拨千斤。

下次系统慢了,别急着加机器。先看看HTTP缓存有没有用到位。说不定,加了反而是浪费。

当然,如果你把Cache-Control设成了no-store然后问为啥慢——那问题就不在缓存了,在脑子。

相关文章

我见过最烂的 API 设计,连 PM 都看不下去了
你的SQL执行计划:95%的程序员都没看懂那张该死的表格
数据库连接池:你好好的应用,怎么就开始抽风了?
面试能背八股文,生产却还在全表扫描:SQL优化的八个反直觉真相
RESTful API 设计翻车现场:那些年我们一起写过的烂接口
SQL优化这条路,走过的人都说”太难了”

发布评论