Age、Date 与新鲜度计算
Age 和 Date 头部的含义,以及浏览器如何根据 RFC 9111 计算缓存是否新鲜。
#tech / dev / frontend
#type / concept
#status / evergreen
#platform / browser
#tech / network
[!info] related notes
Age、Date 与新鲜度计算
一句话定义
Age 告诉浏览器这个响应在缓存中已经待了多久;Date 告诉浏览器这个响应是什么时候由源站生成的。两者配合 max-age 决定缓存还剩多少新鲜时间。
Age 头部
Age: 1800
- 表示响应从源站生成后,已经在缓存节点(CDN、代理、浏览器)中存在了 1800 秒
- 每经过一个代理/CDN 节点,
Age值会累加 - 浏览器收到时,
Age反映的是整条链路上的总缓存时间
代理如何累加 Age
源站 → CDN(缓存 10 分钟) → 浏览器
源站返回 Cache-Control: max-age=3600。CDN 缓存了 10 分钟后浏览器请求命中:
- CDN 响应
Age: 600(10 分钟 = 600 秒) - 浏览器收到后,剩余新鲜时间 = 3600 - 600 = 3000 秒
Date 头部
Date: Thu, 22 May 2026 08:00:00 GMT
- 表示源站生成响应的时刻
- 每个 HTTP 响应都应该包含
Date - 如果
Date缺失或与本地时钟偏差过大,新鲜度计算可能不准确
时钟偏移问题
如果源站时钟比浏览器快 5 分钟:
- 浏览器看到
Date是”未来”的时间 apparent_age会被算为 0(因为response_time - date_value < 0)- 实际上缓存可能已经不新鲜了,但浏览器认为它还很新
新鲜度计算(RFC 9111 Section 4.2)
浏览器判断缓存是否新鲜的简化公式:
freshness_lifetime = max_age - age - response_delay
其中:
| 变量 | 含义 |
|---|---|
max_age | Cache-Control: max-age=N 中的 N |
age | 响应中的 Age 头部值 |
response_delay | 浏览器发出请求到收到响应的耗时(同源通常可忽略) |
如果 freshness_lifetime > 0,缓存仍新鲜;否则进入协商缓存。
完整公式(RFC 9111 原文)
apparent_age = max(0, response_time - date_value)
corrected_age_value = age_value + (response_time - request_time)
corrected_initial_age = max(apparent_age, corrected_age_value)
freshness_lifetime = max_age - corrected_initial_age
response_delay = response_time - request_time 是浏览器到上一跳的网络延迟。对于直接从源站拿到的响应,age_value 通常为 0,公式退化为 freshness_lifetime ≈ max_age。
实际示例
示例 1:无代理(最常见)
源站 → 浏览器
Cache-Control: max-age=3600
Age: 0(或无 Age 头)
浏览器剩余新鲜时间 ≈ 3600 秒。
示例 2:经过 CDN
源站 → CDN(缓存 10 分钟)→ 浏览器
Cache-Control: max-age=3600
Age: 600
浏览器剩余新鲜时间 = 3600 - 600 = 3000 秒。
示例 3:多级代理
源站 → CDN(5 分钟)→ 公司代理(2 分钟)→ 浏览器
Cache-Control: max-age=3600
Age: 420(5 min + 2 min)
浏览器剩余新鲜时间 = 3600 - 420 = 3180 秒。
启发式新鲜度
当响应既没有 Cache-Control: max-age 也没有 Expires 时,浏览器可以用启发式算法估算新鲜度:
freshness_lifetime = (date_value - last_modified_value) * 10%
这就是为什么有些资源即使没设缓存头,短时间内刷新也不会重新请求——浏览器在用启发式新鲜度。
[!warning] 启发式新鲜度是浏览器的”猜测”行为,不同浏览器实现不同。生产环境应始终显式设置
Cache-Control。
常见误解
- “设置了
max-age=3600就一定缓存 3600 秒” → 不一定,经过代理后Age会缩短实际新鲜时间 - “
Age是浏览器自己加的” → 不是,Age是缓存节点(CDN/代理)添加的 - “没有
Age头就说明没经过缓存” → 不一定,有些代理可能不添加Age,或者Age: 0