新闻详情

新闻详情

首页 / 资讯中心 / 详情

HTTP缓存机制详解:强缓存、协商缓存与CDN配置实战

发布时间:2026/9/28 9:39:24来源:尧图网络
HTTP缓存机制详解:强缓存、协商缓存与CDN配置实战
1. 先搞清楚资源加载到底在哪一环变慢了去年我接手一个老项目的性能优化首页首屏资源大大小小加起来有 80 多个请求打开一次要 3.8 秒。我第一反应是上 CDN、改压缩、搞分包结果一顿操作下来二次打开只快了 200 毫秒。后来用 DevTools 一个个请求看才发现大部分资源每次刷新都在重新下载缓存根本没生效。那一刻我才意识到很多人对资源加载和缓存机制的理解其实停留在好像有个缓存具体怎么工作不清楚的状态。这一篇要聊的就是这个基础中的基础资源从服务器到你屏幕的这一路到底经过哪几道关卡每一关的缓存是怎么决策的为什么有些资源明明设了缓存却不生效以及发布新版本时怎么避免用户看到旧页面的惨案。无论你是前端、后端还是做性能优化的这套机制都是绕不开的地基。先说结论资源加载的快慢往往不是你网络不好而是你的缓存策略压根没把资源留在离用户近的地方。理解了下面这套链路你能解释大部分线上性能问题也能在设计缓存时有据可依。2. HTTP缓存的两大基石强缓存与协商缓存的适用边界我一直觉得不懂 HTTP 缓存的性能优化等于在盲人摸象。浏览器对资源的缓存决策核心就落在两组头的博弈上强缓存走 Cache-Control 和 Expires协商缓存看 ETag 和 Last-Modified。2.1 Cache-Control 的优先级与常见误区强缓存的含义很直接浏览器在某个时间段内压根不发请求直接用本地副本。这个时间段由响应头决定常见写法是Cache-Control: max-age3600意思是这一个小时内同一资源的请求直接命中内存或磁盘缓存网络请求数为 0。这里有一个第一个常见误区很多人以为设置了 Expires 就够了。Expires 是 HTTP/1.1 之前的方案它的值是绝对时间比如Expires: Wed, 21 Oct 2025 07:28:00 GMT。问题在于客户端和服务器时钟不一致时这个时间判断就废了。所以现代实践中以Cache-Control的max-age为准它计算的是相对时间从资源响应那一刻开始计时——Expires 和 Cache-Control 同时存在时浏览器只认 Cache-Control。第二个误区是max-age0和no-cache的区别。很多人觉得这俩一样其实完全不同max-age0资源立即过期浏览器立刻发请求验证但服务器如果返回 304资源依然可以复用本地副本不重复下载 body。no-cache字面意思容易误导人其实它并不是不缓存而是缓存前必须先验证。它允许存储但每次使用前要跟服务器确认资源是否新鲜。第三个更隐蔽的坑是no-store。这个才是真正的禁用一切缓存响应和请求都不能存到任何地方适合包含敏感信息的接口。但你要是把静态资源也加了no-store那基本等于放弃了强缓存带来的所有性能收益每次刷新都全量下载。顺带说一句public和private的语义也常被理解错。private不代表只有本人可见或不能缓存它指的是中间节点比如 CDN 或代理服务器不允许缓存只有浏览器可以。带用户身份信息的 HTML 页面通常用private避免 CDN 把带 Cookie 的响应缓存下来分发给别人。2.2 ETag 与 Last-Modified 怎么选当强缓存失效后浏览器不会直接放弃治疗而是带着条件发请求去问服务器我这个副本还新鲜吗 这个问的动作就是协商缓存。协商缓存依赖两个检查维度维度响应头请求头原理时间Last-ModifiedIf-Modified-Since比较文件修改时间内容指纹ETagIf-None-Match比较资源内容的哈希值我第一次用 Last-Modified 时踩过一个典型的坑文件内容没变但服务器时间戳变了比如部署时 touch 了文件客户端带着旧的 If-Modified-Since 过来服务器一看时间不一致就回 200 全量下发。其实内容一模一样白白浪费了一次完整传输。所以只要条件允许优先用基于文件内容生成的 ETag而不是时间戳。ETag 的精度是粒度级别的哪怕文件只改了一个字节指纹也会变化这才是真正内容级的协商。当然ETag 也不是没有代价。对于集群部署的服务器如果每台机器的 ETag 生成算法不一致比如基于 inode 生成同一资源在不同节点上的 ETag 不一样会导致缓存命中率下降。这种情况要么统一生成算法要么干脆用 Last-Modified 兜底。3. 缓存的层级结构不是只有浏览器缓存这一层说我那 80 个请求的项目除了浏览器还涉及了 Service Worker、CDN、甚至后端应用缓存。资源加载的提速本质上是把每一层的缓存都安排明白。我习惯按离用户由近到远排序内存缓存 → 磁盘缓存 → Service Worker → CDN 边缘节点 → 源站。每一层都有自己的判定逻辑越靠前延迟越低。3.1 浏览器内存缓存与磁盘缓存的决策逻辑浏览器的 memory cache 优先级最高读取速度极快但容量小、生命周期短页面关闭后基本就清空了。disk cache 容量更大可以跨会话持久化。两者的命中不是开发者直接控制的而是浏览器根据资源类型、大小、使用频率自动决策。图片、脚本这类体积大、复用率高的资源更容易进磁盘缓存。这里有个容易被忽略的点地址栏输入网址回车、按 F5 刷新、按 CtrlF5 强制刷新三者的缓存策略完全不同。普通回车/点击链接走完整缓存逻辑强缓存没失效就直接用本地。F5 刷新浏览器会带上Cache-Control: max-age0类的条件请求头即使强缓存还在有效期内也会礼貌地问一下服务器相当于强制走一遍协商缓存。CtrlF5直接禁用一切缓存所有资源带Cache-Control: no-cache方式请求服务器基本都会返回完整内容。这解释了为什么线上反馈我这边页面是旧的你强制刷新一下就好了——用户大概率是按了 F5但 HTML 被强缓存盖住了F5 也没用必须 CtrlF5 或清缓存。3.2 CDN 边缘节点缓存与回源机制CDN 的存在是把源站资源分发到离用户最近的边缘节点。它的核心逻辑是用户请求边缘节点边缘节点如果没命中缓存才回源站拉取并按照响应头的缓存规则决定要不要把资源缓存下来。这里最核心的概念是回源。我见过很多团队的 CDN 缓存命中率低排查下来发现是源站返回的资源没有 Cache-Control 头或者带了一个privateCDN 拿到响应后按规定不能缓存。所以给静态资源加public, max-age31536000, immutable是 CDN 场景下的黄金配置意味着边缘节点和浏览器都可以放心大胆地长期缓存。CDN 缓存还有个额外维度s-maxage。这是专门给共享代理和 CDN 用的指令优先级高于max-age。如果你希望浏览器缓存时间短一些、CDN 缓存时间长一些就可以这样写Cache-Control: private, max-age3600, s-maxage86400这句的意思是浏览器按 1 小时缓存CDN 按 1 天缓存。这种分层缓存策略在前后端同源的场景里特别实用。3.3 Service Worker夹在浏览器与网络之间的调度员如果你做的是 PWAService Worker 就是比 HTTP 缓存更主动的一层。它不依赖响应头而是通过 fetch 事件拦截请求用 JavaScript 直接决定用缓存、走网络、还是先给缓存再后台更新。我用过最舒服的组合是stale-while-revalidate策略命中缓存先返回给页面让首屏秒开然后在后台发起网络请求更新缓存。用户在几乎无感知的情况下拿到内容下一次访问自然是最新的。这种体验是纯 HTTP 缓存很难做到的因为 HTTP 缓存里的新鲜度是服务器说了算而 Service Worker 里是你自己说了算。不过 Service Worker 的版本更新机制也容易踩坑新版本脚本发布后很多用户还是运行旧版本因为 install 阶段的更新触发可能需要等到导航请求。所以我一般建议配合skipWaiting和clients.claim使用让新版本的 Service Worker 尽快接管页面。但尽快接管也意味着你要格外注意新旧版本资源的一致性否则容易出现新壳配旧核的错位。4. 版本更新与缓存失效发布时最头疼的问题缓存机制设计得越激进发布新版本的阻力越大。这是一个天然矛盾想让用户加载快就要缓存得久想让用户看到新内容就要及时失效。工程上解决这个矛盾的手段不是去清缓存而是让资源的 URL 跟着内容变化。4.1 内容指纹与文件名 Hash 策略最经典的方案是内容 hash 命名。构建工具会为每个文件的文件名生成一段 hash内容变了hash 变了文件名也就变了。浏览器看到的是一个全新的 URL自然不会用旧缓存。这样你可以放心大胆地把 hash 文件的 Cache-Control 设置成一年甚至更久——反正内容变化时 URL 会变旧的缓存永远不会被再次请求。这里的关键在于hash 的粒度。我见过有些项目把所有 JS 打进一个 bundle 里内容随便改一改整个文件 hash 变一次意味着整包重新下载。更好的做法是按路由分割代码公共依赖单独抽 chunk。这样改某个页面的代码只有对应的 chunk 和公共依赖的 hash 变化其他 chunk 继续命中缓存。HTML 文件本身则相反它的缓存时间要极短或者用no-cache每次都走协商。因为 HTML 是所有资源的入口清单它不更新浏览器就不知道新 hash 的资源存在。这也是为什么业界常说HTML 必须新鲜静态资源必须过期很久。4.2 线上事故里的缓存失效排查链路我这里要说一个真实经历的调试经过供参考。有一次发布后用户群里开始反馈功能没更新但我自己在带无痕参数的页面上看明明是新版本。问题定位的链路是这样的第一步我先在 Chrome DevTools 的 Network 面板看 HTML 请求的响应头发现Cache-Control: max-age600。这意味着 HTML 被浏览器缓存了 10 分钟。别人发布后立刻刷新拿到的还是旧 HTML里面的资源引用还是上一次构建的 hash。这就是页面看起来完全没变的直接原因。第二步再看静态资源请求发现它们响应头里Cache-Control: max-age31536000但由于 HTML 里引用的还是旧 hash 的文件名浏览器拿到的本来就是缓存里的旧资源——即使没有缓存也不会去请求新文件。第三步溯源为什么 HTML 会带 600 秒缓存。排查后发现是 Nginx 配置里对text/html统一加了这个头本意是静态页面也优化下加载却忘记了 HTML 是所有资源的索引。解决方式是对 HTML 用Cache-Control: no-cache并配合 ETag 做协商验证。这个排查过程的核心思路是先看响应的缓存策略是否符合预期再看请求引用的 URL 是否指向了正确的资源版本。两步都对了缓存才可能是错的。4.3 灰度发布与局部缓存清理如果你用灰度发布还要考虑 CDN 缓存的问题。源站已经切成新版本了CDN 边缘节点上如果还有旧资源缓存就会有部分用户持续命中旧缓存。这时你有几个手段通过 CDN 控制台对指定 URL 做主动刷新/预热。回源时带版本号查询参数例如app.js?v20250315让 URL 与版本关联。用 CDN 的缓存 tag 功能按批清理某次发布涉及的文件。我个人的经验是与其事后清理不如在设计时就保证旧 URL 永远不可复用。内容指纹 正确的新鲜度策略能覆盖绝大多数更新没生效的场景。5. 实测中的几个细节命中率、请求头与浏览器差异理论说完了落到实操上有些细节会直接影响你调试的效率。5.1 用 DevTools 读懂缓存命中情况在 Chrome DevTools 的 Network 面板里最容易被忽略的是 Size 列。它显示(memory cache)或(disk cache)说明请求走了强缓存没有实际网络传输。如果显示具体的字节数再看一下 Status Code状态 304协商缓存命中服务器没返回 body只返回了没变的标记。状态 200 但 Size 显示具体大小完整下载。状态 200 且 Size 显示(from disk cache)直接本地命中。另外可以打开 Network 面板的 Cache 标签页新版 Chrome 需要右键勾选能直接看到每条请求的缓存命中状态、缓存存储名字和对应的缓存条目。这些数据比感觉快没快要靠谱得多。还有一个细节如果你在无痕模式下调试页面加载的是一次全新的缓存空间命中率数据会和正常访问完全不同。所以测性能时别在无痕模式或者频繁 DevTools 开开关关的状态下下结论先清一次缓存再做完整的首次加载和二次加载对比。5.2 几个常用且稳妥的配置参考不同资源类型缓存策略应该有区分。下面是我在实践中比较常推荐的一套基准配置可以根据自己项目微调资源类型Cache-Control 建议说明HTML 页面no-cache每次协商验证确保入口最新带 hash 的 JS/CSSpublic, max-age31536000, immutable一年缓存内容变了换 URL图片/字体public, max-age86400一天为基准可自行调整用户数据接口private, no-store避免任何中间层缓存这个配置里immutable 是一个很值得用的指令。它告诉浏览器这个资源在过期前绝对不会变那么即便用户按 F5 刷新浏览器也会直接跳过重新验证连协商请求都不发。配合内容 hash 的文件名这个指令几乎零风险但收益很实在。当然兼容性上需要注意immutable 在 HTTP 协议规范里并没有正式定义而是 Chrome 等浏览器的扩展支持。对于不支持它的浏览器效果等同于没有这个指令不会有负面影响所以放心用。5.3 容易被忽略的请求头优先级问题最后强调一个我见过很多团队搞混的点当请求头里的Cache-Control和响应头冲突时以请求头为准。浏览器在刷新、强制刷新时会主动带上max-age0或no-cache这会让服务器的严格缓存策略被临时绕过。CDN 在判断是否缓存时也会看请求头里的Authorization、Cookie等字段。带认证信息或动态参数的请求很少有静态资源那么强的可缓存性。所以当你在排查为什么资源没被缓存时别只盯着响应头也要打开请求头看有没有Cache-Control: no-cache这类干扰项。往往是请求头里的一个设置让服务器认为这个请求不能复用缓存。6. 从这套机制延伸出的几个实战判断搞明白缓存机制以后很多线上问题就变得可预测了。比如用户反馈列表页数据是旧的我会先猜是接口缓存策略设得太长而不是数据库没更新比如发布后有人是新版本有人是旧版本我会先看 CDN 缓存清理了没有而不是怀疑构建产物有问题。再分享一个我常用的判断口诀加载慢先看资源是否可缓存更新没生效先看 URL 是否变化缓存命中率低先看响应头的 Cache-Control 是否约等于没设。这三个问题覆盖了我在项目里遇到的九成缓存相关疑难杂症。最后留一个小技巧如果你维护的是老旧项目短时间内没法大改构建配置可以先从 Nginx 层把 HTML 和静态资源分开设置缓存头开始。这一步成本最低收益最直接而且不会影响业务代码。等后续有条件了再逐步引入内容指纹、CDN 分层和 Service Worker把缓存体系一层层搭起来。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 2026/9/28 9:42:31

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 网站做好了没人访问,这是很多老板最头疼的事。你花大价钱做的官网,设计精美、功能齐全,但打开一看,流量为零,咨询为零。这时候你才意识到,问题不在“做没做”,而在“怎么快速做出来并推向市场”。面…

阅读更多 →
昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践 2026/9/28 9:42:24

昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践

昇腾910B上跑DeepSeek多机分布式推理,很多人卡在第一眼:MindIE、HCCL、ranktable、hccn_tool,每个词都眼熟,串起来就不是那么回事。实际踩过一圈之后你会发现,真正决定能不能跑起来的不是模型代码,而是通信…

阅读更多 →
从CANoe到TSMaster:车载总线测试工具链迁移实战指南 2026/9/28 9:42:24

从CANoe到TSMaster:车载总线测试工具链迁移实战指南

搞车载总线测试的工程师,电脑里大概率都装着一套CANoe。我最早接触CANoe是刚入行那会儿,跟着前辈在项目里做网络测试,从报文发送、DBC解析到UDS诊断,基本全是靠Vector这套工具撑起来的。说实话,CANoe确实是这个行业的标…

阅读更多 →
从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地 2026/9/28 9:42:23

从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地

1. 日榜的"热度"到底是怎么算出来的先别急着收藏仓库。每天打开 GitHub 的 Trending 页面,你看到的是过去 24 小时内 Star 增量最高的仓库,周榜和月榜则分别看一周、一个月内的增量。官方没有公开完整排序算法,但用久了会发现&…

阅读更多 →
【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架 2026/9/28 9:42:23

【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南 2026/9/28 9:42:23

OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南

模型评测人工智能大模型AI 评测 【免费下载链接】opencompass OpenCompass is an LLM evaluation platform, supporting a wide range of models from OpenAI, Anthropic, Gemini, Qwen, GLM, DeepSeek, etc, across 100 datasets covering knowledge, reasoning, coding, scie…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉