5.1 缓存层次:从浏览器到服务器


文档摘要

5.1 缓存层次:从浏览器到服务器 本节摘要:缓存是性能优化中杠杆率最高的一招——最好的请求是被免掉的那个。本节画出从浏览器到后端的四层缓存地图:浏览器私有缓存、共享代理缓存、httpd 的 modcache 磁盘缓存、应用层缓存,讲清新鲜度过期与协商校验两种机制、Cache-Control 头的配置方法、modcache 的启用与典型坑,最后给一套"静态资源长缓存、动态内容协商缓存"的完整策略。 先明确学习目标 阅读完本节,你应当能够: 画出四层缓存地图并说明每层存什么、谁判定; 区分强缓存与协商缓存及其状态码表现(200 from cache 与 304); 用 Cache-Control 与 Expires 配置静态资源的缓存策略;

5.1 缓存层次:从浏览器到服务器

本节摘要:缓存是性能优化中杠杆率最高的一招——最好的请求是被免掉的那个。本节画出从浏览器到后端的四层缓存地图:浏览器私有缓存、共享代理缓存、httpd 的 mod_cache 磁盘缓存、应用层缓存,讲清新鲜度过期与协商校验两种机制、Cache-Control 头的配置方法、mod_cache 的启用与典型坑,最后给一套"静态资源长缓存、动态内容协商缓存"的完整策略。

先明确学习目标

阅读完本节,你应当能够:

  1. 画出四层缓存地图并说明每层存什么、谁判定;
  2. 区分强缓存与协商缓存及其状态码表现(200 from cache 与 304);
  3. 用 Cache-Control 与 Expires 配置静态资源的缓存策略;
  4. 启用 mod_cache 并避开"缓存了不该缓的内容"的经典事故;
  5. 设计带指纹文件名的长缓存方案。

四层地图:缓存不止一处

说到"给网站加缓存",新手想到的往往是装一个模块。实际上从用户到数据之间天然存在四层缓存位,每层的存法与判定者都不同:

存在哪 存什么 谁判定可否使用
浏览器私有缓存 用户设备 页面资源 浏览器按响应头
共享代理/CDN 缓存 网络中间层 可共享的响应 中间层按响应头
httpd 服务器缓存 服务器磁盘/内存 后端与生成结果 httpd 按配置与响应头
应用缓存 应用进程/外部存储 数据与计算结果 应用代码

设计缓存策略时的思考顺序应当从外往内:能停在浏览器里的请求连服务器都不用到,这是最便宜的;停不到的看能不能停在 CDN;再停不到才落到服务器缓存;最后才是应用缓存。多数性能事故的根因是顺序反了——服务器缓存做得精致,响应头却写得让浏览器每次都来问。

图 5-1 四层缓存地图与请求止跳点

图 5-1 四层缓存地图与请求止跳点

两种机制:新鲜度与协商

HTTP 缓存的一切围绕两种机制运转。

新鲜度(强缓存):响应头直接声明"这份内容在某时刻前是新鲜的"。新鲜期内,浏览器连请求都不发。声明靠 Cache-Control 的 max-age:

<IfModule mod_expires.c> ExpiresActive On # 图片与字体:一年,"永不担心过期"的底气来自下一节的指纹方案 ExpiresByType image/png "access plus 1 year" ExpiresByType font/woff2 "access plus 1 year" # css/js:一个月 ExpiresByType text/css "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" # html:不加长缓存,它是引用其他资源的入口,必须保持新鲜 ExpiresByType text/html "access plus 0 seconds" </IfModule>

协商(条件请求):新鲜期过了,浏览器带着上次响应留下的验证凭证(Last-Modified 时间戳或 ETag 内容指纹)来问"变了吗"。没变,服务器只回一个 304,不带正文;变了,回全新 200。协商的代价是一个完整往返,所以它是"安全网"而不是目标状态。

这套机制里最经典的工程方案是内容指纹:静态资源文件名里带内容哈希(如 app.a3f9c2.css),内容一变文件名就变。于是 CSS/JS 可以放心设一年长缓存——反正在你眼里,文件名变了就是新文件,旧文件名的缓存"永远新鲜、永远正确"。html 不缓存,它负责引用新文件名。这就是上面配置的内在逻辑:入口文件零缓存保新鲜,指纹资源超长缓存保性能

⚠️ 常见坑:给 html 也设了长缓存。用户改版后喊"网站没更新"——他的浏览器还在用缓存里的旧 html,引用着旧资源。html 必须保持"每次都可能问服务器"的弹性。

第三层:mod_cache 让 httpd 自己记住

前两层靠响应头驱动,第三层是 httpd 亲自把生成结果存下来。典型场景:某个动态页(比如 CMS 渲染的栏目页)生成要 300 毫秒,但内容十分钟才变一次——让它每请求都重新生成就是浪费。mod_cache 的写法:

LoadModule cache_module modules/mod_cache.so LoadModule cache_disk_module modules/mod_cache_disk.so CacheEnable disk /news CacheDefaultExpire 600 CacheHeader On # 服务重启不清缓存(键基于URL) CacheIgnoreNoStore On

含义:/news 前缀的响应存到磁盘,默认 600 秒内直接复用。CacheHeader On 会在响应头里加一条标记(X-Cache: HIT 或 MISS),验证命中不用猜:

curl -sI https://www.example.com/news/list | grep -i x-cache # 第一次 HIT 前显示 MISS,第二次起显示 HIT

mod_cache 的两个经典事故必须写进部署检查单。事故一:缓存了不该缓的。个人化内容(登录后的页面、含用户名的响应)被缓存,A 用户看到了 B 用户的数据——这是安全事故而非性能问题。防线:确认应用的动态个人化路径不在 CacheEnable 前缀内;应用对不可缓存响应显式发 Cache-Control: no-store 或 private(mod_cache 尊重这些头)。事故二:只缓存了 200 却不知道 5xx 也会被短存。失败响应被缓存导致故障被放大,用 CacheDisable 与负面 TTL 控制兜底。

启用顺序上的一个冷知识:mod_cache 位于输出过滤链,因此它缓存的内容是压缩前的版本(deflate 在其后),命中后的响应仍会按当前请求头决定压不压——顺序正确时两者不打架,别手工调换过滤顺序。

策略落地:一个完整的分层方案

把四层拼成一套生产策略,以新闻站为例:

  • 静态资源(图片、字体、带指纹的 css/js):指纹命名 + 一年长缓存 + 前面叠 CDN;httpd 侧无需额外工作,Expires 头交给 CDN 与浏览器执行。
  • 公共动态页(栏目页、文章页):应用侧渲染后 60–600 秒可复用 → mod_cache 磁盘缓存 + X-Cache 观测;同时发 s-maxage 让 CDN 也能存。
  • 个人化动态页(用户中心、购物车):一路显式 no-store/private,任何一层都禁止缓存。
  • API 读接口:按业务容忍度给短 TTL(5–30 秒)的应用缓存,避免惊群穿透到数据库。

验证工具用浏览器开发者面板的网络视图最直观:看每个资源的"from cache / 304 / 200"三种状态占比,就是这套策略的健康指标。

本节要点回顾

  • 思考从外往内:浏览器 > CDN > 服务器缓存 > 应用缓存,越靠外越便宜;
  • 两种机制:强缓存零请求,协商缓存 304 仍付一个往返;
  • 指纹方案:入口 html 零缓存、指纹资源一年长缓存,两者配合是黄金搭档;
  • mod_cache:CacheEnable 划范围、X-Cache 验命中、个人化路径绝不入池;
  • 过滤顺序:缓存存的是压缩前内容,顺序别手工干预;
  • 健康指标:网络面板里 from cache 的占比就是缓存策略的成绩单。

💡 一句话记住本节:最快的响应是没发出的响应,其次是不用重新生成的响应。

下一节看归途的第二道工序:内容怎么瘦身、连接怎么提速。


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