先记住这个答案
当响应同时包含 Last-Modified 和 ETag 时,条件请求应优先使用 ETag(即发送 If-None-Match),因为 ETag 能精确标识资源版本,不受时间粒度限制。Last-Modified 只有秒级精度,如果资源在一秒内被修改多次,其值可能不变,导致缓存误判为未修改而返回过时内容。因此客户端应依赖 ETag 进行验证,Last-Modified 仅作为兜底或在无 ETag 时使用。
- ETag 优先于 Last-Modified
- 秒级精度漏检同秒修改
- ETag 可精确到任意粒度
验证器的比较规则
HTTP 验证器分为强验证器和弱验证器。ETag 默认是强验证器,只要字节内容变化,ETag 就必须变化;而 Last-Modified 是时间戳,只能精确到秒。当资源在 1 秒内连续两次修改,第二次修改的 Last-Modified 可能与第一次相同(若在同一秒完成),导致条件请求误判为未修改。
RFC 9110 定义 ETag 比较时,强验证器要求字节完全一致,弱验证器(W/ 前缀)允许语义等价。Last-Modified 比较时使用 HTTP 日期格式,必须精确到秒。因此 ETag 能提供更可靠的版本验证,尤其适合频繁更新的资源。
秒级精度漏检的实例
某 API 返回 JSON 数据,服务器快速更新:09:00:00.500 写入数据 A,09:00:00.900 写入数据 B。Last-Modified 均显示为 09:00:00,因为时间戳只到秒。客户端缓存了 A 的响应,携带 If-Modified-Since: 09:00:00 发起请求,服务器发现 Last-Modified 不大于该时间,返回 304,客户端继续使用 A,但实际数据已是 B。
若响应带有 ETag,服务器生成 ETag 为 "v1" 和 "v2",客户端携带 If-None-Match: "v1",服务器比较发现不匹配,返回 200 和新数据,正确更新。此场景说明 ETag 的精度优势直接避免数据过期。
适用前提与限制
ETag 优先的前提是服务器正确实现 ETag 生成规则,且资源每次变化时 ETag 都更新。若服务器使用弱 ETag(W/ 前缀),它可能不随字节变化而变化(如语义等价),此时强校验能力降低,但仍比 Last-Modified 可靠。
Last-Modified 并非全无用处:当客户端只存储了 Last-Modified 而没有 ETag(如旧缓存)时,可退化为 If-Modified-Since。另外,某些服务器可能因性能原因不生成 ETag,此时只能依赖时间。但设计上应尽量提供 ETag,并确保其唯一性。
容易答错的地方
- 认为 Last-Modified 足够精确
- Last-Modified 只精确到秒,无法区分 1 秒内的多次修改。若认为它接近实时,会设计出过期缓存风险的方案。正确做法是依赖 ETag 或在文件内容变化时同步更新时间戳。
- 同时发送两个条件头
- RFC 9110 规定当 If-None-Match 存在时,服务器必须忽略 If-Modified-Since。因此不必同时发送,否则只是增加请求头大小,且可能因两者值不一致导致歧义。客户端应只发送 If-None-Match。
面试官还会怎么问?
If-Modified-Since 和 If-None-Match 同时出现时服务器如何处理?
根据 RFC 9110,If-None-Match 优先,服务器必须忽略 If-Modified-Since。所以实际以 If-None-Match 为准,避免冲突。
强 ETag 和弱 ETag 对条件请求有何影响?
强 ETag 用于 If-Match / If-None-Match 时可做字节级精确比较;弱 ETag 只能做语义等价比较,因此不能用于 If-Range 等要求强验证的场合。
启发式缓存中 Last-Modified 的作用是什么?
当响应没有明确新鲜度信息时,缓存可基于 Last-Modified 估算新鲜期(通常为 10%),但该方法不精确,只作为兜底策略。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。