缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。
当 web 缓存发现请求的资源已经被存储且未过期,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。
HTTP强缓存 在chrome浏览器中返回的200状态会有两种情况:
-
from memory cache
:从内存中获取,一般缓存更新频率较高的js、图片、字体等资源 -
from disk cache
:从磁盘中获取,一般缓存更新频率较低的js、css等资源
这两种情况是chrome自身的一种缓存策略,这也是为什么chrome浏览器响应的快的原因。注意:其他浏览返回的是已缓存状态,没有标识是从哪获取的缓存。
前端缓存的分类 |
本文着重讲的是HTTP缓存
HTTP缓存的分类 |
根据是否需要重新向服务器发起请求来分类:
- 强制缓存
- 协商缓存
根据是否可以被单个或者多个用户使用来分类:
- 私有缓存
- 共享缓存
分类一:强制缓存与协商缓存 |
强制缓存 |
强制缓存在缓存数据未过期失效的情况下,直接读取浏览器缓存,不发送网络请求,并返回200。
但若生效期间改了这些资源,页面上是拿不到的,因为它不会再向服务器发请求了。
这种情况就是我们在开发种经常遇到的,比如你修改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强缓存,所以
Ctrl + F5
一顿操作之后就好了。
跟强制缓存相关的header头属性有:
Pragma/Cache-Control/Expires
协商缓存 |
当第一次请求时服务器返回的响应头中没设有
Cache-Contro
l或
Expires
;或者设置的
Cache-Control
和
Expires
已经过期了;或者把它设置成了
no-cache
时(即不走强缓存),那么
浏览器第二次请求时就会与服务器进行协商,
与服务器端对比判断资源是否进行了修改更新。
如果服务器端的资源没有修改,那么就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码,服务器就会返回更新后的资源并且将缓存信息一起返回。
跟协商缓存相关的header头属性有:
ETag/If-Not-Match 、Last-Modified/If-Modified-Since
请求头和响应头需要成对出现。
分类二:私有缓存与共享缓存 |
以下为
MDN caching
分类
私有缓存(浏览器级缓存) |
私有缓存只能用于单独用户。浏览器缓存拥有用户通过 HTTP 下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。它同样可以提供缓存内容的离线浏览。
Cache-Control: Private
共享缓存(代理级缓存) |
共享缓存可以被多个用户使用。例如,ISP 或你所在的公司可能会架设一个 web 代理来作为本地网络基础的一部分提供给用户。这样热门的资源就会被重复使用,减少网络拥堵与延迟(各种中间仓库)。
服务端缓存又分为 代理服务器缓存 和 反向代理服务器缓存,其实广泛使用的 CDN 也是一种服务端缓存,用来缓存图片、文件等静态资源。
Cache-Control: Public
缓存的作用 |
减少网络带宽消耗 |
无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。使用缓存可以降低带宽压力和网络流量。
降低服务器压力 |
减少对服务器的请求,降低服务器的压力
减少网络延迟,加快页面打开速度 |
对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验
HTTP缓存会缓存哪些资源? |
常见的 HTTP 缓存只能存储 GET 响应,
对于其他类型的响应则无能为力。缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存)。
普遍的缓存案例:
- 一个检索请求的成功响应: 对于 GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应。
- 永久重定向: 响应状态码:301。
- 错误响应: 响应状态码:404 的一个页面。
- 不完全的响应: 响应状态码 206,只返回局部的信息。
- 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应。
缓存规则 |
这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。
它们分别从
新鲜度和校验值
两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
新鲜度(过期机制) |
理论而言,资源被缓存后可以永久存储,实际缓存空间有限,定期会删除一些缓存副本,此过程称为
缓存驱逐
。
服务器更新了资源,客户端是不知道的,那就预设一个过期时间,在有效期内,该资源就是新鲜的。过期后,该资源变陈旧,但是此资源还不会直接被清除。当客户端发起请求时,会把检测到的陈旧资源附加
If-None-Match
头,然后发给服务器,服务器检查到在这个资源还是没变化的,返回304(不会带有实体数据,同时更新
max-age
的值),表示这个资源你还可以继续使用。这样,节省了一定(本需重新请求后实体信息)的宽带。若服务器通过
If-None-Match
或
If-Modified-Since
判断后发现已过期,那么会带有该资源的实体内容返回。
校验值(验证机制) |
服务器返回资源的时候有时在控制头信息带上这个资源的实体标签
Etag
(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
与缓存有关的HTTP消息报头 |
哪怕请求中同时设置了多个作用相同的消息报头,也不用担心会相互影响发生错乱,它们之间是有优先级别的。
比如:强制缓存相关的header头有:
Pragma/Cache-Control/Expires
,在HTTP1.1中同时设置了这几个,优先级:
Pragma >Cache-Control >Expires
以下表格中类型的值有一个称为“通用首部字段”,表示 请求报文 和 响应报文 都能用上的字段。
规则 | 消息包头 | 值/示例 | 类型 | 作用 |
---|---|---|---|---|
新鲜度 | Pragma | no-cache | 通用首部字段 |
告诉浏览器忽略资源的缓存副本,每次访问都需要去服务器拉取 【http1.0中存在的字段,在http1.1已被抛弃,使用Cache-Control替代,但为了做http协议的向下兼容,很多网站依旧会带上这个字段】 |
Expires | Mon, 15 Aug 2020 03:56:47 GMT | 实体首部字段 |
启用缓存和定义缓存时间。告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求 【http1.0中存在的字段,该字段所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代】 |
|
Cache-Control | no-cache | 通用首部字段 | 告诉浏览器忽略资源的缓存副本,强制每次请求直接发送给服务器,拉取资源,但不是“不缓存” | |
no-store | 强制缓存在任何情况下都不要保留任何副本 | |||
max-age=[秒] | 指明缓存副本的有效时长,从请求时间开始到过期时间之间的秒数 | |||
public | 任何路径的缓存者(本地缓存、代理服务器),可以无条件的缓存改资源 | |||
private | 只针对单个用户或者实体(不同用户、窗口)缓存资源 | |||
Last-Modified | Mon, 15 Aug 2020 03:56:47 GMT | 实体首部字段 |
告诉浏览器这个资源最后的修改时间。服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端 【只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间】 |
|
If-Modified-Since | Mon, 15 Aug 2020 03:56:47 GMT | 请求首部字段 | 其值为上次响应头的Last-Modified值,再次向web服务器请求时带上头If-Modified-Since。web服务器收到请求后发现有头If-Modified-Since则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息体内),并更新Last-Modified的值,HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304(响应体无需包含新内容),并更新Last-Modified的值,告知浏览器继续使用所保存的cache | |
校验值 | ETag | “fd56273325a2114818df4f29a628226d” | 响应首部字段 | 告诉浏览器当前资源在服务器的唯一标识符(生成规则又服务器决定) |
If-None-Match | “fd56273325a2114818df4f29a628226d” | 请求首部字段 | 当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etag声明,则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后发现有头If-None-Match则与被请求资源的相应校验串进行比对,决定返回200或304 |
Cache-Control与Expires |
作用一致,指明当前资源的有效期,
Cache-Control
的选择更多,设置更细致,且优先级高于
Expires
。
Expires
的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用
Cache-Control: max-age=秒
替代。
Last-Modified/ETag与Cache-Control/Expires |
配置
Last-Modified/ETag
的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;
Cache-Control/Expires
则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,
Cache-Control/Expires
的优先级要高于
Last-Modified/ETag
。
即当本地副本根据
Cache-Control/Expires
发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(
Last-Modified
)或实体标识(Etag)了。
一般情况下,使用
Cache-Control/Expires
会配合
Last-Modified/ETag
一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时
Last-Modified/ETag
将能够很好利用304,从而减少响应开销。
Last-Modified与ETag |
Last-Modified
标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度
如果某些文件会被定期生成,当有时内容并没有任何变化,但
Last-Modified
却改变了,导致文件没法使用缓存。
有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。
Last-Modified与ETag
是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
浏览器的缓存的实现 |
html
页面缓存的设置主要是在
<head>
标签中嵌入
<meta>
标签,这种方式只对页面有效,对页面上的资源无效。静态资源的缓存一般是在web服务器上配置的,常用的web服务器有:nginx、apache。
1.使用HTML Meta标签(页面)
<meta http-equiv="Pragma" content="no-cache">
<!- Pragma是http1.0版本中给客户端设定缓存方式之一 仅限IE有效 且不一定要加上Pragma-->
<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">
<meta http-equiv="Cache-Control" content="no-cache">
2.使用缓存有关的HTTP消息报头(后端)
php:
<%
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", -10);
%>
nodeJs:
当然并不是所有的请求头都需要开发人员去指定,
浏览器会根据自己的安全策略机制结合用户的行为会自动添加一些请求头,
这就是为什么我们并没有设置这些请求头或者响应头,但我们查看却能看到的原因。
使用HTTP缓存的建议 |
强缓存加随机数 |
强缓存情况下,只要缓存还没过期,就会直接从缓存中取数据,就算服务器端有数据变化,也不会从服务器端获取了,这样就无法获取到修改后的数据。解决的办法有:在修改后的资源加上随机数,确保不会从缓存中取。
<script type="text/javascript">
document.write("<script type='text/javascript' src='js/cours_details.js?random=" + Math.random() + "'></script>")
</script>
<script type="text/javascript">
document.write("<link rel='stylesheet' type='text/css' href='css/cours_details.css?random=" + Math.random() + "' />");
</script>
减少304的请求 |
尽量减少304的请求,因为我们知道,协商缓存每次都会与后台服务器进行交互,所以性能上不是很好。从性能上来看尽量多使用强缓存。
兼容Firefox 写成Cache-Control: no-cache,no-store |
在Firefox浏览器下,使用
Cache-Control: no-cache
是不生效的,其识别的是
no-store
。这样能达到其他浏览器使用
Cache-Control: no-cache
的效果。所以为了兼容Firefox浏览器,经常会写成
Cache-Control: no-cache,no-store
。
浏览器HTTP请求流程 |
第一次请求
再次请求:
用户行为与缓存 |
用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5刷新 | 无效(BR重置max-age=0) | 有效 |
Ctrl+F5刷新 | 无效(重置Cache-Control=no-cache) | 无效(请求头丢弃该选项) |
实例分析 |
假设当前有这么一个 index 页面,返回的响应信息如下
cache-control: max-age=72000
expires: Tue, 20 Nov 2018 20:41:14 GMT
last-modified: Tue, 20 Nov 2018 00:41:14 GMT
三种刷新方式的区别:
输入url回车 |
这种情况下会
根据实际设计的缓存策略去判断。
浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。
(耗时:高)
? 分析:
-
由于该例子没有设置
no-cache
和
no-store
,所以默认先走强缓存路线。根据
cache-control
(
expires
优先级低)判断缓存是否过期,若没有过期则此时返回 200(from cache)。 -
若本地缓存已经过期再走协商缓存路线,根据之前的
last-modified
值去与服务器比对,若这个时间之后没有改过则去读取本地缓存,返回 304(not modified)。 -
否则返回新的资源,状态码 200(ok),并更新返回响应的
last-modified
值。
按刷新按钮、F5 刷新、网页右键“重新加载” |
直接使强缓存过期,并在请求上附加协商缓存的标识。
浏览器直接对本地缓存文件过期,但是会带上
If-Modified-Since、If-None-Match
标识,服务器会检测文件新鲜度,返回结果可能是304,也可能是200.
(耗时:中等)
这种情况下,实际是浏览器将
cache-control
的
max-age
直接设置成了 0,让缓存立即过期,直接走协商缓存路线。发送的请求头如下:
cache-control: max-age=0
if-modified-since: Tue, 20 Nov 2018 00:41:14 GMT
按Ctrl+F5 |
浏览器不仅直接对本地文件过期,而且也不会带上
If-Modified-Since、If-None-Match
,相当于之前从来没有请求过,返回结果200。浏览器会强行设置
no-cache
,强制获取最新的资源
(耗时:高)
cache-control: no-cache
pragma: no-cache
============================================================================================
参考文档:
HTTP caching
浏览器 HTTP 协议缓存机制详解
浏览器缓存机制详解
浅谈浏览器http的缓存机制
彻底弄懂强缓存与协商缓存
一文读懂http缓存