深入浏览器缓存机制
我们在开发过程中可能经常看到 200 (memory cache)
200 (disk cache)
304 Not Modified
的状态码,他们分别是什么意思?
这里先有个大体的概念,200 状态码是浏览器的强制缓存机制,304 则是协商缓存机制,这两个缓存机制是通过 HTTP 报文的缓存标识来生效的。
这里我们分析一个普通的 HTTP 请求的请求报文,省略了不影响缓存的消息头
第一次打开 hawtim.github.io/blog ,GET 请求
general
Request URL: https://hawtim.github.io/blog/
Request Method: GET
Status Code: 200
Remote Address: 185.199.111.153:443
Referrer Policy: no-referrer-when-downgrade
request headers
:authority: hawtim.github.io
:method: GET
accept: text/html
# 注意 cache-control 和 pragma
cache-control: no-cache
pragma: no-cache
#============================
response headers
content-encoding: gzip
content-type: text/html; charset=utf-8
date: Thu, 21 May 2020 03:49:51 GMT
# 注意 cache-control etag expires last-modified
cache-control: max-age=600
etag: W/"5ec1ee1c-8ac"
expires: Thu, 21 May 2020 03:51:50 GMT
last-modified: Mon, 18 May 2020 02:08:28 GMT
#=============================================
server: GitHub.com
status: 200 # 现在的状态是 200
第二次 GET 请求 (CTRL + R / Command + R)
general
Request URL: https://hawtim.github.io/blog/
Request Method: GET
Status Code: 304
Remote Address: 185.199.111.153:443
Referrer Policy: no-referrer-when-downgrade
request headers
:authority: hawtim.github.io
:method: GET
accept: text/html
# 注意 cache-control if-modified-since if-none-match
cache-control: max-age=0
if-modified-since: Mon, 18 May 2020 02:08:28 GMT
if-none-match: W/"5ec1ee1c-8ac"
# =================================================
response headers
content-encoding: gzip
content-length: 924
content-type: text/html; charset=utf-8
date: Thu, 21 May 2020 04:13:25 GMT
# 注意 cache-control etag expires last-modified
cache-control: max-age=600
etag: W/"5ec1ee1c-8ac"
expires: Thu, 21 May 2020 04:23:25 GMT
last-modified: Mon, 18 May 2020 02:08:28 GMT
#=============================================
server: GitHub.com
status: 304 # 状态变成 304
看完上面的例子,你可能还对上面注意的消息头,比如
cache-control
expires
last-modified
if-modified-since
etag
if-none-match
有点懵,不知道它们是怎么在浏览器的缓存机制中生效的,没关系,我们下面来分析缓存过程
缓存过程分析
从图片可以看出
- 浏览器每次对资源发起请求的时候,会先在浏览器的缓存查找对应资源的请求结果和缓存标识
- 浏览器每次拿到返回的请求结果会将结果和缓存标识存入浏览器的缓存中
以上两点就是浏览器缓存机制的关键,确保了每个请求缓存存入与读取。根据是否需要向服务器重新发起 HTTP 请求,我们将缓存过程分为两部分,第一部分是强制缓存,第二部分是协商缓存。
强制缓存
强制缓存就是在浏览器的缓存中查找请求结果,并根据该结果的缓存标识决定是否使用该结果。有以下三种情况:
- 第一种情况,不存在缓存结果和缓存标识,直接向服务器发送请求,同上面的缓存过程分析
- 第二种情况,存在缓存结果和缓存标识,但是验证后已经失效,则使用
协商缓存
- 第三种情况,存在缓存结果和缓存标识,且验证有效,则直接返回该结果
强制缓存相关的请求头
- pragma
- expires
- cache-control
pragma
Pragma
是 HTTP/1.0 中规定的通用首部,它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器,因为 那时候 HTTP/1.1 协议中的 Cache-Control 还没出来
Pragma: no-cache
与 Cache-Control: no-cache
效果一致
expires
Expires
是 HTTP/1.0 中规定的通用首部,它用来指指定资源缓存到期的时间。时间是绝对时间,受客户端时间影响。
cache-control
Cache-Control
是 HTTP/1.1中通用首部,同样用来指指定资源缓存到期的时间,max-age 设置的时间的单位为秒,是相对时间,不受客户端时间影响,优先级比 Expires 高,主要取值为:
-
public
:所有内容都将被缓存(客户端和代理服务器都可缓存) -
private
:所有内容只有客户端可以缓存,Cache-Control 的默认取值 -
must-revalidate
:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。 -
no-cache
:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定 -
no-store
:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 -
max-age=600
:缓存内容将在 600 秒 (任意正整型数字)后失效
使用场景
- 关闭缓存
Cache-Control: no-store
- 缓存静态资源:
Cache-Control: public, max-age=31536000
- 重新验证缓存:
Cache-Control: no-store
或max-age=0
细心的朋友可能发现了,一开始的例子里,第二次请求的时候 github 就是用了 max-age=0来向服务端校验我的文件是否有更改。
浏览器的缓存的存放位置
以 github 的博客请求为例,状态码是灰色 200 则代表使用了强制缓存,请求对应的 size 值则代表缓存存放的位置,分别为 memory cache
和 disk cache
,那这两者有什么差别,什么情况用 memory 什么情况用 disk?
memory cache (内存缓存)
内存缓存会将编译解析后的文件,直接存入该进程的内存,占据一定的内存资源,方便下次使用时快速读取,但是一旦该进程关闭,则该进程的内存会被清空。
disk cache(硬盘缓存)
硬盘缓存是直接将缓存写入硬盘文件中,读取缓存需要进行 I/O 操作,读取该缓存并解析,速度比内存缓存慢。
使用的实际情况分析
还是以博客的页面为例,没有禁用缓存的情况下,多次刷新页面。就会如出现如 memory cache 和 disk cache 都有的情况。
然后关闭这个标签页,然后重新打开这个页面,则会出现全部都是从 disk cache 的情况。
这里有一点要注意的是,虽然 rem.js 是在 memory cache 中获取的,但实际上它也存在 disk cache,只是 memory cache 的优先级更高。
在浏览器中,浏览器会将 js 和 base64 的图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而 css 文件,图片后缀的图片文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)
协商缓存
下面我们来讲讲强制缓存的第二种情况,强制缓存标识失效,走协商缓存的过程。
在强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
- 协商缓存生效,返回 304 和 Not Modified
- 协商缓存失效,返回 200 和请求结果
协商缓存的标识在响应报文的 HTTP 头中和请求结果一起返回给浏览器。
控制协商缓存的字段分别有:
Last-Modified / If-Modified-Since
下次请求时把上次返回的 Last-Modified
作为 If-Modified-Since
的值,服务端发现请求头中有 If-Modified-Since
字段,服务端将该值与资源的最后修改时间比对,大于则重新返回资源,否则返回 304 代表 资源无更新,可继续使用缓存。
Etag / If-None-Match
下次请求时把上次返回的 Etag
作为 If-None-Match
的值,服务端将 If-None-Match
的值与资源的 Etag
比对,一致则返回 304,代表资源无更新,继续使用缓存文件,不一致则重新返回资源文件,状态码 200
注 Etag / If-None-Match 优先级高于 Last-Modified / If-Modified-Since,同时存在则只有前者生效。
总结
综合上面的分析,现在看文章前面的第一次请求和第二次请求的变化,应该就能很好理解了。
在浏览器的缓存机制中,强制缓存优先于协商缓存。强制缓存的标识(Expires 和 Cache-Control),如果生效则使用缓存,如果不生效则发起请求进行协商缓存的标识验证。(Last-Modified / If-Modified-Since和 Etag / If-None-Match),若协商缓存失效,则重新获取请求结果,再传入浏览器缓存中;生效的话,则继续使用缓存。
参考文章
《浏览器缓存看这一篇就够了》
《彻底理解浏览器的缓存机制》
相关 UML 链接
协商缓存失效过程
浏览器缓存全过程