HTTP 协议的缓存是通过 6 个报文头完成的,通过两层协商使 web 资源能够不那么频繁地在服务器与客户端之间传递,从而使服务器不必多次处理相同的请求,节约了流量,提高浏览速度。
以从客户端到服务器的顺序,第一层协商为 Cache-Control 与 Expires;第二层协商为 Last-Modified 与 Etag 。
本章不涉及与 HTTP 相关性不大的缓存代理(如 CDN)和客户端缓存(如 manifest)。
相关的报文头
Cache-Control
请求/响应报文头,缓存控制字段,也就是用于控制资源生命周期,是 http/1.1 引入的属性。它支持多值,值用逗号分隔。作用方不仅限于客户端,有些指令还将作用在中间的缓存服务器上。
例子:
Cache-Control: private, max-age=0, no-transform
Cache-Control 的指令梳理
代号:客户端(C)、缓存代理(P)、服务器(S)。
注:中括号表示可选项,尖括号表示参数。
属性名 | 发起方 | 作用方 | 说明 |
---|---|---|---|
public | S | C/P | 任何一方都可以缓存该资源。 |
private[=<header>] | S | C/P | 只允许客户端缓存,不允许缓存代理缓存。可选指定针对某些头部。 |
no-cache[=<header>] | C/S | C/P | 缓存代理不缓存;客户端缓存该资源,但每次都要询问是否更新,可以等价 max-age=0 。当服务器发起时可选指定针对某些头部。 |
no-store | C/S | C/P | 任何一方都不缓存该资源。 |
max-age=<second> | C/S | C/P | 设置缓存存储的最大周期,也就是说在这个秒数内不发起新请求。当客户端发送此指令给缓存代理时,代理能满足要求则直接返回给客户端,不必再次访问服务器。 |
no-transform | C/S | P | 缓存代理不可更改媒体类型,这样做可防止代理执行压缩图片等类似操作。 |
s-maxage=<seconds> | S | P | 缓存代理可缓存的最长时间。 |
must-revalidate | S | C/P | 可缓存但一旦资源过期必须再向源服务器进行确认,如果无法访问服务器则向客户端报 504 Gateway Timeout 。优先级高于 max-stale。 |
proxy-revalidate | S | P | 与 must-revalidate 作用相同,但它仅适用于缓存代理。 |
max-stale[=<seconds>] | C | P | 客户端要求缓存代理该时间内(默认不限时间)的资源无论缓存有没有过期都返回给客户端。 |
min-fresh=<seconds> | C | P | 客户端要求缓存代理返回至少还未过指定时间的缓存资源,可以理解成限定了资源的最小生命期,在这生命期内才算有效。 |
only-if-cached | C | P | 客户端要求缓存代理只返回有效的缓存,不需要向服务器对有效性进行确认,如果没有缓存则报 504 Gateway Timeout 。 |
补充说明
在 HTTP/1.1 中 Cache-Control 是优先级最高的缓存相关头部,部分决定是否缓存的指令(public、private、no-store)会起到直接决定的作用,也就是说如果决定不缓存了,那就不会再进行下一步关于是否过期的判断。在 HTTP/1.0 中 Cache-Control 会被忽略,降级为对 Expires 过期时间的判断。
Expires
响应报文头,代表资源过期时间,在过期之前缓存会一直保存,并且不会向服务器发起请求。由服务器返回提供,是 HTTP/1.0 的属性,在 HTTP/1.1 环境并且与 Cache-Control 共存的情况下,优先级要低。
Expires 的功能基本与 Cache-Control 的 max-age 指令相似,但它是指定一个过期时间点,而 Cache-Control 的 max-age 是指定了过期前的秒数。
例子:
Expires: Wed, 04 Jul 2020 08:26:05 GMT
Last-Modified
响应报文头,资源最终修改时间,由服务器告诉客户端。
例子:
Last-Modified: Wed, 23 May 2020 09:59:55 GMT
If-Modified-Since
请求报文头,与 Last-Modified 相对应,浏览器把服务器最后一次给的 Last-Modified 返回。服务器将以此进行对比,判断资源是否需要更新,如果请求的资源都没有过更新,则返回状态码 304 Not Modified 的响应。
例子:
If-Modified-Since: Thu, 15 Apr 2004 00:00:00 GMT
Etag
响应报文头,ETag 是 HTTP/1.1 标准开始引入的,对 Last-Modified 的补充。它是一种可将资源以字符串形式做唯一性标识的方式。服务器会为每份资源分配对应的 ETag 值。当资源更新时,ETag 值也需要更新。
ETag 的强弱之分
- 强 ETag:不论实体发生多么细微的变化都会改变其值。
- 弱 ETag:只用于提示资源是否相同。只有资源发生了根本改变,产 生差异时才会改变 ETag 值。这时,会在字段值最开始处附加 W/。
例子:
ETag: "usagi-1234"
ETag: W/"usagi-1234"
为什么需要 ETag
- 一些周期性修改的文件,修改时间变了但内容没变,此时不希望重新获取;
- 一些文件修改非常频繁,比如1秒内修改了多次,Last-Modified 只能精确到秒;
- 一些服务器不能得到文件修改的精确时间。
额外注意
- ETag 没有规定生成的算法,每个服务器生成都可能不一样;
- 分布式系统里多台计算机间文件的 Last-Modified 必须一致,以免负载均衡到不同机器导致对比失败,因此分布式系统要统一 ETag 算法。
If-None-Match
请求报文头,是一种客户端向服务器提条件的方法,它与报文头 If- Match 作用相反。一般客户端把服务器最后一次给的 ETag 值通过 If-None-Match 返回,服务器将以此进行对比,判断资源是否需要更新。
例子:
If-None-Match:58b66ccbe349d0d931df877c00d8101d037243dc
协商流程
以下假定资源已经获取过一次,并且运行在HTTP/1.1环境下,现在进行二次访问。
流程图如下:
说明:
- 客户端是有可能因为缓存原因不向服务器发起任何请求的,图中 200 From Cache 就是这种情况。
- 服务器根据回传的 If-Modified-Since 与 Last-Modified 比对,如果不同则说明这个文件修改过,需要更新。但在这种判断精度是秒,如果是一秒内的改动,就需要进一步对比回传的 If-None-Match 与 ETag 的值。
- 服务器返回 304 Not Modified 的意思就是不需要重新获取新资源,直接使用本地缓存即可。
缓存多久合适
生存时间(TTL)指令告诉浏览器应该缓存某个资源多久,也就是 Cache-Control 或 Expires 的值。
找到给定资源的最佳TTL值并没有完美的科学方法。
指导原则:
- 纯静态内容,例如图片或带版本的数据,可以在客户端永久缓存;
- CSS/JS 和个性化资源,缓存时间大约是会话(交互)平均时间的两倍;
其他类型资源取决于新数据对旧数据的容忍极限。
浏览器操作对 HTTP 缓存的影响
用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5刷新 | 无效 | 无效 |
缓存改进方案
md5/hash 缓存
通过不缓存 html,为静态文件添加 MD5 或者 hash 标识,解决浏览器无法跳过缓存过期时间主动感知文件变化的问题。
CDN缓存(代理缓存)
CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。