HTTP 缓存验证字段里,Last-Modified 可能是最不起眼却又最常被忽视的一个,很多 CDN 架构师一谈条件请求就只盯着 ETag,仿佛 Last-Modified 只是 HTTP/1.0 的遗物,但真实线上环境中,它依然是回源验证的基石之一,今天不聊炫技,只把 Last-Modified 的底裤扒干净,聊聊它和 ETag 的明争暗斗,以及你到底该怎么选。
Last-Modified 到底是什么?
Last-Modified 是响应头中的一个字段,表示源站上资源的最后修改时间,它的核心用途有两个:
- 缓存有效期计算:配合 Cache-Control 的 max-age 或 Expires,告诉 CDN 节点和浏览器这个资源“新鲜”到什么时间点,如果超过有效期,缓存需要重新验证。
- 条件请求验证:客户端(或 CDN 节点)发起请求时带上
If-Modified-Since头,值就是上次响应里的 Last-Modified,源站比对资源实际修改时间:如果没变,返回 304 Not Modified,不发 body;如果变了,返回 200 和新资源。
这里有个容易混淆的点:Last-Modified 本身不是缓存策略,它只是一个“时间戳证据”,缓存能不能用、能用多久,由 Cache-Control 决定;Last-Modified 只负责在缓存过期后回答“这个资源变没变”的问题。

Last-Modified 过时了吗?CDN 缓存验证中的老好人与新贵
Last-Modified 的硬伤:秒级精度与“伪更新”
技术拆解要到位,必须说清楚它的两个天然缺陷:
- 精度只有秒,同一秒内资源被修改两次,但内容已经不同,Last-Modified 不会变化,If-Modified-Since 比对时会认为“没变”,从而错误地返回 304。
- 修改时间变化 ≠ 内容变化,比如文件被 touch 一下(时间戳更新,内容没动),Last-Modified 变了,但内容完全一样,此时条件请求会回 200 并重新传输 body,浪费带宽。
正因为这两点,ETag 出现了,ETag 是内容实体标签,通常是哈希值,能精确感知内容变化,粒度远高于秒,且不会因 touch 触发伪更新,看起来 ETag 全面碾压 Last-Modified,那为什么 CDN 场景里 Last-Modified 还没被淘汰?
方案对比:Last-Modified vs ETag,不是二选一
在 CDN 架构中,我们经常同时启用两种验证机制,典型的条件请求流程是这样的:
客户端 → CDN 节点:带 If-None-Match: <etag>,同时带 If-Modified-Since: <last-modified>
CDN 节点 → 源站:转发这两个头(如果源站支持)
源站:先比对 ETag,不一致就回 200;一致再看 If-Modified-Since,通过则回 304。
这里有个优先级:HTTP 规范明确,如果请求同时带 If-None-Match 和 If-Modified-Since,源站必须忽略后者(只要 ETag 能匹配),所以实际起作用的是 ETag,Last-Modified 只作为兜底——当 ETag 缺失或无法生成时使用。
但反过来,当 CDN 作为中间层时,Last-Modified 有它的独特价值:
| 维度 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 内容级 |
| 计算开销 | 几乎为零(读文件 mtime) | 需要计算哈希,大文件有 CPU 成本 |
| 分布式存储 | 不稳定(各副本 mtime 可能不一致) | 一致则哈希一致) |
| 代理/服务器支持 | 所有 HTTP 服务器都有 | 部分静态文件服务默认不开启 |
| 安全风险 | 可能泄露修改时间 | 不泄露信息 |
| 断点续传 | 配合 Range 做 If-Range 验证 | 配合 Range 做 If-Range 验证 |
在源站是 Nginx 或 Apache 的简单场景,ETag 默认由 inode+size+mtime 生成,存在跨机器不一致问题,inode 变化会频繁触发回源,所以很多 CDN 厂商在回源层会剥离源站 ETag,统一用自己的规则生成(比如基于内容 MD5),或者干脆只信任 Last-Modified。
适用场景分析:什么时候必须用 Last-Modified?
不是所有资源都能轻易算出 ETag,实际业务里,以下场景 Last-Modified 依然是主力:
-
大文件分发(视频、安装包),文件动辄几个 GB,如果回源验证每次都要算一次完整哈希,服务器 CPU 会爆,而 Last-Modified 只需读取文件元数据,开销可忽略,配合 Range 请求,用 If-Range + Last-Modified 做断点续传的条件验证,既精准又省钱。
-
动态生成且无法预计算哈希的响应,比如服务端渲染的页面、每次生成带时间戳的 JSON,ETag 可以生成,但如果每次响应内容都不同(比如用户行为打点接口),ETag 反而导致每次都回 200,缓存形同虚设,此时用 Last-Modified 配合较短 max-age,能达到“过期后如果源站修改时间变化则重拖,否则 304”的轻量验证。
-
源站是对象存储(OSS/S3)且未开启 ETag 强验证,很多对象存储的 ETag 是多部分上传时生成的,不保证内容强一致;而 Last-Modified 是对象元数据里最可靠的字段,CDN 回源时用 Last-Modified 做条件请求,简单直接。
-
弱 ETag 不可用的老系统,某些遗留 CMS 或应用服务器不输出 ETag,但几乎都带 Last-Modified,对 CDN 宁可有一个秒级精度的验证器,也比没有强。
选型建议:别迷信“ETag 一定更好”
作为 CDN 架构师,我给你几条实在的建议:
-
默认双发,但控制优先级,CDN 回源时把
If-None-Match和If-Modified-Since都带上,但明确告诉源站:ETag 为准,Last-Modified 兜底,这样既能享受 ETag 的精确度,又能在源站关闭 ETag 时自动降级。 -
优先用 Last-Modified + max-age=0,如果接口数据更新频率不高,但又不能缓存太久,设置
Cache-Control: no-cache,同时保留 Last-Modified,这样每次请求都会回源验证,源站通过 304 省流量,CDN 通过只转发头部省内网带宽,这种情况下,ETag 反而因为每次响应体不同而完全失效。 -
大文件下载场景,坚决用 Last-Modified 做 If-Range,当一个 4G 文件下载中断后重新续传,客户端带
If-Range: <last-modified>,CDN 节点如果发现本地缓存的文件修改时间没变,就只返回 206 的缺失部分,此时不用 ETag,因为计算大文件哈希的成本太高。 -
多级缓存架构,要让源站 ETag 唯一且稳定,如果你的源站有多台 Web 服务器,务必关闭默认 ETag(Nginx 的 etag 指令改为 off),或者统一改造为内容 MD5 型 ETag,否则不同源站的 ETag 不一致会引发“缓存抖动”——CDN 节点每次都认为缓存失效,回源率飙升,这种情况下,Last-Modified 因为基于标准时间,反而天然一致。
-
安全敏感场景慎用 Last-Modified,某些内部系统不希望泄露文件最后修改时间,建议用 ETag 替代,或者在源站层把 Last-Modified 置为固定值(等于资源上线时间)。
一句话总结
Last-Modified 不是过时的老古董,它是 HTTP 缓存里的“瑞士军刀”:精度不高,但通用、便宜、稳定,ETag 是手术刀,精准但挑场景,CDN 架构的核心不是选边站,而是让两者在合适的位置发挥各自优势——能用 ETag 的地方用 ETag,用不了的地方用 Last-Modified,二者协同,才是健康的缓存验证体系。
下次有人问“Last-Modified 过时了吗”,你可以告诉他:在 CDN 回源验证这场戏里,它依然是那个不可或缺的配角。
发表评论