缓存的分类:
HTTP缓存根据缓存的节点,分为私有缓存和共享缓存。
私有缓存是绑定到特定客户端的缓存——通常是浏览器缓存。由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应。
共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应。共享缓存可以进一步细分为代理缓存和托管缓存。
代理缓存是指,除了访问控制的功能外,一些代理还实现了缓存,以减少网络流量,这通常不由服务开发人员管理,因此必须有恰当的HTTP标头等控制。
托管缓存由服务开发人员明确部署,以降低源服务器负载并有效地交付内容,包括反向代理、CDN和service worker 与缓存 API 的组合。托管缓存的特性因部署的产品而异。在大多数情况下,可以通过 Cache-Control 标头和配置文件或仪表板来控制缓存的行为。例如,HTTP 缓存规范本质上没有定义显式删除缓存的方法——但是使用托管缓存,可以通过仪表板操作、API 调用、重新启动等实时删除已经存储的响应。
下面我们重点来说说浏览器缓存。
什么是浏览器缓存?
浏览器缓存其实就是浏览器保存通过HTTP请求获取的所有资源,是浏览器将网络资源存储在本地的一种行为。当客户端向服务器请求资源的时候,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取,而不是从原始服务器中提取资源。浏览器的缓存机制是根据HTTP请求的响应头Response header
的缓存标识进行的,对客户端浏览器缓存的实际设置工作是在服务器资源中设置的。
常见的浏览器缓存只能缓存GET请求响应的资源,对于其他类型的响应则无能为力。
为什么使用缓存?
- 缓存减少了冗余数据传输,节约了网络资源
- 缓存降低了服务器压力
- 缓存加速了页面的展示
浏览器缓存过程
浏览器缓存都是从第二次请求开始的:
-
浏览器第一次加载资源时,服务器返回200,并在
Response header
中回传资源的缓存参数Expires/Cache-Control、Last-Modified/ETag等;浏览器将资源文件从服务器上下载下来,并把response header
及该请求的返回时间一起缓存;
-
下一次访问资源时,会先判断浏览器本地的缓存时否过期,如果没有过期,则命中强制缓存,不向服务器发送请求,直接从本地缓存读取该文件;如果缓存过期,则向服务器发送
header
带有If-None-Match
和If-Modified-Since
的请求; -
服务器收到请求后,会根据
If-None-Match(Etag优先)
和If-Modified-Since(Last-Modified)
的值判断被请求的文件有没有做修改,如果一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag
和Last-Modified
值并返回200;
浏览器缓存策略
通常浏览器缓存策略分为两种:强制缓存(Expires,cache-control)和协商缓存(Last-modified,Etag),并且缓存策略都是通过设置 HTTP Header 来实现的。
强制缓存
强制缓存,在缓存数据未失效的情况下(即Catch-Control:max-age
没有过期、Expires
的缓存时间没有过期或启发式缓存没有过期),不会再向服务器发送请求,直接从浏览器缓存中读取资源。
强制缓存生效时,HTTP状态码为200。这种方式页面的加载速度是最快的,性能也是最好的,但是在这期间,如果服务器端的资源修改了,页面就拿不到最新的资源了。这种情况就是我们在开发中经常遇到的,比如改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强制缓存,所以Ctrl + F5一顿操作之后就好了。
强制缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
- Expires:在
Response header
中设置的缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。浏览器再次加载资源的时候,如果还在这个过期时间内,则命中强制缓存。
Expires:Thu, 09 Dec 2021 04:40:13 GMT
- Cache-Control:指定 max-age 字段,表示缓存的内容将在一定时间后失效。
Cache-Control:max-age=300
代表在这个请求正确返回的5分钟内再次加载资源,就会命中强缓存。
Cache-Control指令 | 作用 |
---|---|
public | 表示响应可以被客户端和代理服务器缓存 |
private | 表示响应只可以被客户端缓存 |
max-age=30 | 表示缓存30秒后就过期,需要重新请求服务器 |
s-maxage=30 | 覆盖max-age,作用一样,只在代理服务器中生效 |
no-store | 表示不缓存任何响应 |
no-cache | 表示资源可以缓存,但立即失效,下次会发起请求验证资源是否过期 |
max-stale=30 | 表示30秒内,即使缓存过期,也使用该缓存 |
min-fresh=30 | 希望在30秒内获取最新的响应 |
must-revalidate | 对于客户端的每次请求,代理服务器必须向服务器验证缓存是否过时 |
两者区别:
- Expires是http 1.0的产物,Catch-Control是http 1.1的产物;
- 两者同时存在的时候,Catch-Control优先级高于Expires;
- 在某些不支持HTTP 1.1的环境下,Expires就会发挥用处,现阶段他的存在只是一种兼容性的写法;
- Expires是一个具体的服务器时间,如果客户端和服务器时间相差较大,缓存命中与否就不是开发者所期望的。Catch-Control是一个时间段,控制起来比较容易;
此时有一个问题,如果可以缓存的请求没有设置任何缓存信息时,浏览器是不是就完全不会去缓存数据了呢??当然不是,在这种特殊情况下,会触发浏览器的默认缓存策略–启发式缓存。
启发式缓存
如果一个可以缓存的请求没有设置Expires和Cache-Control,但是响应头有设置Last-Modified信息,这种情况下浏览器会有一个默认的缓存策略-启发式缓存。复用多长时间取决于实现,但规范建议存储后大约 10%的时间,即缓存时长=(Date - Last-Modified)*0.1。
目前看来,大部分浏览器都已经实现了,但是彼此也略有不同。
注:只有在服务端没有返回明确的缓存策略时才会激活浏览器的启发式缓存策略。
启发式缓存会带来严重的缓存问题。假设有一个文件没有设置缓存时间,在一个月前更新了上个版本。这次发版后,可能需要等到3天后用户才看到新的内容。如果这个资源还在CDN也缓存了,则问题会更严重。
协商缓存
协商缓存,即在第一次请求服务器,服务器返回数据,并且响应头中带有Catch-Control:max-age
和Expires
,或者Catch-Control:no-catch
。在后续请求中,如果Catch-Control:max-age
和Expires
过期,或者Catch-Control:no-catch
,就会与服务器进行协商,与服务器端对比判断,资源是否进行了修改更新。
- 如果服务器端的资源没有修改,就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。
- 如果数据有更新,会返回200状态码,服务器会返回更新后的资源并且将缓存信息一起返回。
- Etag 和 If-None-Match
Etag是上一次加载资源时,服务器在Response header
中返回的,是对该资源的一种唯一标识。只要资源变化,Etag就会重新生成。浏览器在下一次向服务器发送请求时,会将上一次返回的Etag值放到Request header
中的If-None-Match里,服务器接收到 If-None-Match 的值后,会拿来跟源文件的Etag值作比较,如果相同,则表示资源文件没有变化,命中协商缓存。如果不一致,服务器会直接返回数据。
- Last-Modified 和 If-Modified-Since
Last-Modified是该资源文件最后一次更改时间,服务器会在Response header
中返回,同时浏览器会将这个值保存起来,在下次向服务器发送请求时,放到Request header
中的 If-Modified-Since 里,服务器在接收到后也会做对比,如果相同则命中协商缓存。如果不一致,服务器会直接返回数据。
两者区别:
- 在方式上,Etag是对资源的一种唯一标识,Last-Modified是该文件最后一次更改时间;
- 在精确度上,Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果单个文件在1秒内改变了多次,那么Last-Modified并不能体现出修改,但是Etag每次都会改变,确保了精度。如果是负载均衡的服务器,各个服务器生成的Last-Modified也可能不一致。
如果文件每隔一段时间就会重复生成,但是内容相同,Last-Modified会每次返回资源文件,即便内容相同,但是Etag可以判断出文件内容相同,就会返回304,使用缓存。 - 在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,Etag需要服务器通过算法计算出一个hash值。
- 在优先级上,服务器校验优先考虑Etag。
存储位置
从存储位置来看,浏览器缓存一共分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
- Service Worker:运行在浏览器背后的独立线程,一般可以用来实现缓存功能。因为Service Worker中涉及请求拦截,必须使用HTTPS协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
- Memory Cache:内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。
- Disk Cache:存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
Disk Cache会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。
- Push Cache:(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
关于memory cache 和 disk cache
首次访问某网页或者开启浏览器的 Disable Cache(浏览器的Network下, 与Preserve log同级别),在size 一栏会显示资源大小。
在关闭 Disable Cache 的情况下,再次访问该网页,发现size 一栏显示(memory cache) 或者(disk cache)
状态 | 类型 | 说明 |
---|---|---|
200 | form memory cache | 不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以后,数据将不存在。但是这种方式只能缓存派生资源 |
200 | form disk cache | 不请求网络资源(服务器),直接从磁盘中读取缓存,当kill进程时,数据还是存在。 |
200 | 资源大小 | 从服务器下载最新资源 |
304 | 报文大小 | 携带If-None-Match(Etag优先) 和If-Modified-Since(Last-Modified) 请求服务端,服务器比对后发现资源没更新,返回304,然后从缓存中读取数据 |
用户行为对浏览器缓存的控制
- 地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制;
- F5刷新,浏览器会在请求头中设置
Cache-control: max-age=0
,跳过强制缓存判断,会进行协商缓存判断; - ctrl+F5刷新,浏览器会在请求头中设置
Cache-control: no-cache
,跳过强缓存和协商缓存,直接从服务器拉取资源。
三级缓存原理 (访问缓存优先级)
- 先在内存中查找,如果有,直接加载。
- 如果内存中不存在,则在硬盘中查找,如果有直接加载。
- 如果硬盘中也没有,那么就进行网络请求。
- 请求获取的资源缓存到硬盘和内存。
vue-cli缓存策略
由于打包后的js、css和图片,一般名称都带有hash值,名称中的hash变了,自然会拉取新文件,所以我们可以将这类文件设置为强制缓存,只要文件名不变,就一直缓存。
而html文件则不能设为强制缓存。html如果设置了强制缓存,在缓存有效期内将无法更新,html不更新,其引用的js、css等名称也不会更新,则整个服务都没有更新,只能让用户清除缓存了。所以针对html文件,我们可以设置协商缓存或者直接不使用缓存。因为html文件本身都比较小,可以设置不缓存,nginx配置如下。
server {
listen 80;
server_name yourdomain.com;
location / {
try_files $uri $uri/ /index.html;
root /yourdir/;
index index.html index.htm;
if ($request_filename ~* .*\.(js|css|woff|png|jpg|jpeg)$)
{
expires 100d; //js、css、图片缓存100天
#add_header Cache-Control "max-age = 8640000"; //或者设置max-age
}
if ($request_filename ~* .*\.(?:htm|html)$)
{
add_header Cache-Control "no-store"; //html不缓存 或 add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate"
}
}
}
参考资料:
浏览器的缓存机制
HTTP 缓存
meta标签
Clear-Site-Data–清除当前请求网站有关的浏览器数据