Web 性能优化里”快”这件事,80% 的收益来自缓存。DNS 缓存解决”域名到 IP”的重复查询,HTTP 缓存解决”URL 到资源内容”的重复拉取。两者都有强制缓存和协商缓存两种思路:前者靠”算时间”跳过重复工作,后者靠”问一句”省下重复拉取的带宽。理解这两层缓存的实现方式,是定位”页面慢、版本不更新、CDN 命中率低”等问题的基础。
一、DNS 缓存的 8 种实现方式
DNS 查询从浏览器到根服务器之间存在多级缓存,命中任意一级都能节省一次网络往返。下面是工程里实际生效的 8 种实现方式,按从客户端到递归解析器再到权威的顺序展开。
- 浏览器 DNS 缓存:Chrome、Firefox、Edge 等浏览器内部维护,TTL 由浏览器策略决定(Firefox 默认 60 秒),可在
chrome://net-internals/#dns查看;命中即返回 IP,毫秒级。 - 操作系统 DNS 缓存:Windows 由 DNS Client 服务维护,macOS 由 mDNSResponder 维护,Linux 由 systemd-resolved 或 nscd 维护,所有应用共享,跨进程复用,TTL 取权威服务器下发的值。
- hosts 文件静态映射:
/etc/hosts或C:\Windows\System32\drivers\etc\hosts,没有 TTL 概念,文件里写什么就一直生效;常用于本地开发、内网域名、临时屏蔽广告。 - 路由器 DNS 缓存:家用与企业路由器对局域网内所有设备的查询结果做缓存,重启即清空;多设备共享,命中率最高。
- 本机/局域网自建递归解析器:企业内网部署 unbound、dnsmasq 等本地递归解析器,可自定义转发策略、过滤广告域名、统一记录查询日志。
- 公共递归解析器缓存:Google 8.8.8.8、Cloudflare 1.1.1.1、Quad9 9.9.9.9 等公共 DNS 服务,覆盖全球数亿终端,命中率极高;支持 DoH/DoT 加密查询。
- ISP 运营商递归解析器缓存:运营商自建解析器对本网用户查询结果做缓存,是大多数家庭网络真正命中的那一层;改动 DNS 记录时”全球生效慢”多由它造成。
- 权威 DNS 服务器自身缓存:一些权威服务商(如 Cloudflare DNS、Route 53)会在边缘节点对热记录做缓存,进一步降低对源站的查询压力。
1.1 DNS 缓存命中顺序示例
浏览器输入 www.example.com
↓ ① 浏览器 DNS 缓存(命中即返回)
↓ ② 操作系统 DNS 缓存
↓ ③ hosts 文件
↓ ④ 路由器 DNS 缓存
↓ ⑤ 本地/ISP/公共递归解析器
↓ ⑥ 根 → TLD → 权威解析器(只有全部 miss 才走完)
冷查询通常 20-120 ms;命中任一层缓存都压到 10 ms 以内,浏览器自身命中可压到 1 ms 内。一张含 30 个第三方域名的页面,DNS 耗时差距常常是 200 ms 级别。
1.2 TTL 与生效延迟
DNS 记录本身带 TTL,TTL 越大缓存复用率越高,但解析记录变更后”全球生效”时间也越长。迁移前临时把 TTL 调到 300 秒是常规做法,迁移结束后再调回 86400 秒这类长值。
二、HTTP 缓存的两种核心策略
HTTP 缓存分两层:先看强制缓存是否生效,没命中再走协商缓存。两者是递进而非并列关系。
2.1 强制缓存:本地判断,不发请求
强制缓存的核心是”浏览器自己决定资源是否过期”,完全不与服务器通信。判断依据是两个响应头:
- Cache-Control(HTTP/1.1,主流):
max-age=N表示从响应返回时刻起 N 秒内有效,相对时间,不受客户端本地时钟影响; - Expires(HTTP/1.0,已基本被取代):
Wed, 28 May 2026 10:00:00 GMT形式,写绝对时间,受客户端时差影响。
两者并存时 Cache-Control 优先级更高。命中后浏览器状态栏显示 200 (from disk cache) 或 200 (from memory cache)。
Cache-Control 还提供 no-cache(跳过强制缓存,强制走协商)、no-store(完全禁用缓存)、public(允许 CDN 等中间节点缓存)、private(仅浏览器可缓存)、immutable(声明期内不变化,避免刷新重新校验)等指令,用于精细控制策略。
2.2 协商缓存:服务器验证,节省带宽
强制缓存过期后浏览器不是直接拉新资源,而是把缓存标识发给服务器,由服务器判断是否还能用旧副本。服务器返回两种结果:
- 304 Not Modified:资源没变,浏览器继续用本地副本,响应体通常只有几百字节;
- 200 OK + 新资源:资源已更新,浏览器更新本地缓存。
协商缓存有两条独立路径,可单独使用也可同时配置:
- Last-Modified / If-Modified-Since:服务器返回资源的最后修改时间,浏览器下次请求带上;服务器对比时间判断是否更新。精度只到秒,文件保存但未改内容时会误判。
- ETag / If-None-Match:服务器根据资源内容算一个唯一哈希(强 ETag 用
ETag: "abc",弱 ETag 用ETag: W/"abc"),浏览器下次带上对比;精度高,能识别秒级内修改。
两者并存时 ETag 优先级更高,因为 ETag 是按内容计算的,Last-Modified 是按文件元信息计算的;前者更贴合”内容是否真的变了”这个核心问题。
2.3 强制 vs 协商:一张表看完
| 维度 | 强制缓存 | 协商缓存 |
|---|---|---|
| 是否发请求 | 否 | 是(仅头部) |
| 状态码 | 200 (from cache) | 304 Not Modified / 200 OK |
| 控制字段 | Cache-Control、Expires | ETag/If-None-Match、Last-Modified/If-Modified-Since |
| 适用协议 | HTTP/1.0、HTTP/1.1 | HTTP/1.0、HTTP/1.1 |
| 精度 | 时间粒度(秒级) | ETag 可字节级 |
| 典型场景 | 静态资源(图片、CSS、JS) | 频繁更新或需精确验证的内容 |
| 失效后行为 | 进入协商缓存 | 服务器返回 200 + 新资源 |
三、一次完整 HTTP 缓存决策流程
浏览器处理一个 URL 时按下面顺序判断,命中任一分支即停止:
- 缓存里有吗?没有则直接发请求拿资源;
- 有 → 看 Cache-Control(或 Expires)是否过期,没过期直接用本地副本;
- 过期 → 看是否有 ETag 或 Last-Modified,有则带
If-None-Match/If-Modified-Since问服务器; - 服务器返回 304 → 继续用本地副本;返回 200 → 用新资源并更新缓存。
GET /app.css HTTP/1.1
Host: example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 27 May 2026 10:00:00 GMT
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
304 响应只回响应头、不回响应体,这是它能省带宽的关键。注意 Ctrl+F5 这种”强制刷新”会绕过强制缓存和协商缓存,请求头里不带任何缓存标识,等同于把缓存层整层掀掉。
四、典型场景的缓存策略
把强制与协商两层组合用好,是缓存设计的核心。下面是经过大量线上项目验证的三种典型组合:
- 带哈希文件名的静态资源(如
app.a5d7f8e3.js):HTML 不强制缓存,每次走协商;JS/CSS/图片用Cache-Control: max-age=31536000, immutable强制缓存一年,更新靠改文件名触发。 - 接口数据:用
Cache-Control: no-cache跳过强制缓存,由 ETag/Last-Modified 走协商,命中后只回 304 不回响应体;适合字典、配置这类低频变更数据。 - 敏感信息:用
Cache-Control: no-store完全禁用缓存,每次都从服务器拉;常见于支付、登录态、个人隐私类响应。
# Nginx 静态资源缓存策略示例
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location /api/ {
add_header Cache-Control "no-cache";
etag on;
if_modified_since exact;
}
五、落地时的易错点
缓存策略看着简单,但生产环境里翻车的姿势很多:
- 只配 Cache-Control 不配 ETag:强制缓存过期后无法走协商,每次都要重新下载整资源;
- HTML 配了长 max-age:版本更新后用户长时间看不到新页面;
- CDN 上
Vary配置缺失:移动端/桌面端响应错乱,命中率被人为降低; - DNS 缓存未刷新:迁移后部分用户访问老 IP 长达 TTL 周期,提前调低 TTL 能显著缩短”全球生效”时间;
- 304 响应被错误缓存:某些代理会把 304 响应缓存为 200 资源,导致资源看起来”不变”。
常见问题(FAQ)
Q1:DNS 缓存多久清一次合适?
浏览器缓存由浏览器策略决定,操作系统与递归解析器按权威返回的 TTL 走。生产环境变更 DNS 记录前,先把 TTL 调小到 300 秒再改,可避免长时间”全球不生效”。
Q2:强制缓存和协商缓存哪个先判断?
强制缓存先判断。命中直接用本地副本,状态码 200 (from cache);未命中或被声明为 no-cache 才进入协商阶段,由服务器返回 304 或 200。
Q3:ETag 会不会泄露文件内容?
强 ETag 通常是内容哈希,但默认不暴露内容明文;如需更严格控制,可改为版本号或时间戳作为标识,或改用弱 ETag(W/ 前缀)容忍微小差异。