5.3 静态资源服务与缓存控制


5.3 静态资源服务与缓存控制

出层流量里最重的一类不是接口响应,而是静态资源——图片、脚本、样式表、字体。它们的出层路径不经过业务处理器,却最吃缓存策略:配对了,大部分请求在 304 或本地缓存就结束;配错了,每次刷新都是全量重传。本节把静态服务的装配、强缓存与协商缓存的配方、以及 304 的带宽账一次算清。

静态层就位:koa-static 的完整配置

第 3.2 节装配过 koa-static,这里把参数语义补全:

const serve = require('koa-static'); const path = require('path'); app.use(serve(path.join(__dirname, 'public'), { maxage: 1000 * 60 * 60 * 24 * 30, // Cache-Control 的 max-age,毫秒单位 immutable: true, // 配合内容哈希文件名,告知期间绝不回源验证 index: 'index.html', // 目录默认文件 gzip: true, // 有同名 .gz 文件时直接出,省实时压缩 }));

三条配置原则来自生产教训。其一,maxage 只配给带哈希的文件。构建工具产出的 app.3f9c2a.js 这类文件名带内容哈希,内容变名就变,配一年强缓存都安全;而 logo.png 这种名字固定的文件,强缓存一年意味着"改了图用户半年看不到"。其二,HTML 不配长缓存——它是所有资源的入口,入口被缓存,新版本资源永远没机会露面,HTML 要么不缓存要么短缓存加协商验证。其三,gzip 选项只对"构建期预压缩"生效,即磁盘上真的存在同名 .gz 文件;没有预压缩流水线就别开,让它去干 koa-compress 的活。

强缓存与协商缓存:两套机制各管一段

HTTP 缓存是两层的,很多教程混着讲,这里拆开。

强缓存由 Cache-Control 主导:Cache-Control: public, max-age=2592000, immutable 的意思是"有效期之内,浏览器根本不发请求,直接用本地副本"。省的是整个请求。协商缓存由 ETag 或 Last-Modified 主导:缓存过期后,浏览器带着 If-None-Match: "etag值" 来问"这个版本还能用吗",服务端比对后要么回 200 带新内容,要么回 304 Not Modified 不带内容。省的是响应体,请求本身还是要走一趟。

两者的正确关系是接力而不是二选一:强缓存管"新鲜期内别来烦我",协商缓存管"过期之后低成本续命"。Koa 侧开启协商缓存:

const etag = require('koa-etag'); const conditional = require('koa-conditional-get'); app.use(conditional()); // 必须在最外:解析 If-None-Match / If-Modified-Since app.use(etag()); // 为响应生成 ETag,按内容哈希 app.use(serve('./public'));

koa-conditional-get 负责读取请求侧的条件头,koa-etag 负责为出层响应计算并写入 ETag,两者配合,命中时响应自动变成 304。装配顺序 conditional 在 etag 之前,这是它文档里的第一行,也是最容易看漏的一行。

图 5-2:一次带缓存的静态请求决策路径

图 5-2:一次带缓存的静态请求决策路径

算一笔 304 的带宽账

策略值不值,用数字说话。假设一个 200KB 的脚本文件,日请求上万:

  • 无缓存:每天上万次全量传输,仅这一个文件就是 2GB 级出层流量,CDN 与带宽费用随之走高;
  • 只有强缓存(30 天):新鲜期内接近零流量,但发布新版后,存量用户最长一个月看不到更新——除非文件名带哈希;
  • 强缓存加哈希文件名:新版发布即新 URL,旧缓存自然失效,流量与时效双赢——这是构建流水线的标准形态;
  • 只有协商缓存:每次都发请求,但 304 只回头部(几百字节),流量降到无缓存方案的百分位级。

账算完结论自明:哈希文件名加强缓存是首选,协商缓存是兜底与补充。HTML 入口单独走短缓存或协商,保证新版本资源"永远能被发现"。

⚠️ 常见坑:缓存策略发布后无法单方面收回。Cache-Control 一旦发出,在 max-age 之内客户端与中间缓存都会忠实执行,你改服务端也改不了用户手里已缓存的副本——这就是"配错 maxage 比不配更糟"的原因。拿不准的新资源先用短 max-age(如十分钟)加协商缓存灰度,观察命中与更新延迟,再逐步拉长。

接入 CDN 之后:源站的缓存口径

规模再往上走,静态流量会交给 CDN,此时源站(你的 Koa 服务)的缓存头语义会从"对浏览器说话"变成"对整条缓存链说话"。三条口径要对齐。其一,头就是配置:CDN 大多遵循源站的 Cache-Control——源站说 max-age 一天,边缘节点就缓存一天;你改了源站头,还要确认 CDN 配置没有"忽略源站头"的覆盖项,两边打架时排查会非常痛苦。其二,回源要有保护:缓存大面积失效(新版本发布、CDN 节点抖动)时,海量请求会同时砸回源站——ETag 协商缓存在这里第二次显价值,304 的回源响应几乎不占带宽;更稳的做法是发布前主动预热 CDN,让边缘先填满。其三,动态接口别被误缓存:带 Authorization 头的 API 响应默认不该进共享缓存,必要时显式加 Cache-Control: private, no-store;曾见过网关误把登录用户的响应缓存进边缘节点,造成用户间数据串页的事故——缓存头既是性能工具,也是权限边界。

本节要点回顾

  • 静态层参数三原则:maxage 配哈希文件、HTML 短缓存、gzip 选项只在预压缩时开;
  • 两层缓存各管一段:强缓存省请求、协商缓存省响应体,接力而非二选一;
  • 协商缓存两件套:conditional-get 在前读条件头,etag 在后生成标识,顺序不可反;
  • 带宽账:哈希加强缓存双赢,纯协商把流量压到百分位级,无缓存最贵;
  • 缓存发出去就收不回:新资源从短 max-age 灰度起步,别一步到位配一年。

出层半程到此完整:状态码与头定了骨架、协商与格式填了血肉、静态与缓存管住了最重的流量。下一章视角再拉高一层——当内层的异步出了岔子,错误与进程的稳定性怎么保障。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U