概述
浏览器http缓存,既是网页静态资源服务性能优化的一把利器,也是无数web开发者在工作之初谈之色变的一大难题。
在开发过程中我们极力避免缓存,但在生产环境中,我们又在想尽办法利用缓存。
所以了解浏览器缓存的机制,是一个优秀开发者绕不开的重要基础知识。
各种缓存的命中与否,说到底不过是几个与之相关的http header数据的匹配与校验。如果了解了每个相关header的意义与关系,那么就能将缓存策略运用自如。
http_caches.jpg user-action2.png
cache type
- 200 from cache
直接从本地缓存(有的文章称之为强缓存)中获取响应,返回200状态,chrome网络面板中的size项显示from cache
最快速,最省流量,因为根本没有向服务器发送请求。
-
304 Not Modified
在本地缓存没有命中的情况下,请求头中发送一定的校验数据到服务端,校验成功后服务器返回304 not modified表示资源未被修改,浏览器从本地缓存中获取响应,这种缓存方式通常被称为协商缓存。
快速,发送的数据很少,只返回一些基本的响应头信息,数据量很小,不发送实际响应体。
cache-304.png -
200 OK
以上两种缓存全都失败,服务器返回完整响应。
没有用到缓存,相对最慢。
本地缓存
本地缓存的使用,相当于基于之前服务器的response header,浏览器认为当前的缓存可以直接使用,于是不再与服务器进行任何交互而直接使用缓存的过程。
与本地缓存命中相关的http header:
- Pragma
这个是http1.0时代的遗留产物,该字段被设置为no-cache时(实际上现有的RFC标准标明只有这个可选值),会告知浏览器禁用本地缓存,即每次都向服务器发送请求。
- Expires
http1.0时代用来启用本地缓存的字段,expires值对应一个形如Thu, 31 Dec 2037 23:55:55 GMT的格林威治时间,告诉浏览器缓存实现的时刻,如果还没到该时刻,标明缓存有效,无需发送请求。
但是这个方式有个很明显的问题,就是浏览器与服务器的时间是不能保证一致的,如果时间差距较大,那么会影响缓存管理的结果。
# example:
# vim /etc/nginx/sites-available/your_server_config
location ~* \.(?:css|js)$ {
expires 1d;
access_log off;
add_header Cache-Control "public";
}
这告诉浏览器,在date+1号之前,可以直接使用该文本的缓存副本。但是,可能会因为服务器和客户端的GMT时间不同,会有一定的bug。 所以,这里只提议在长时间缓存的情况下使用。否则,应该选择Cache-Control
- Cache-Control
Cache-Control 头在 HTTP/1.1 规范中定义,取代了之前用来定义响应缓存策略的头(例如 Expires)。当前的所有浏览器都支持 Cache-Control,因此,使用它就够了。
不过,目前大部分服务器都会将两者添加上,因为HTTP规定,如果Cache-Control和expires同时出现的话,expires会默认被覆盖掉。 此时,返回的响应码不再是304(文件未改动),而是200(资源成功访问).
http1.1针对Expires时间不一致的问题,采取了一个十分聪明的设定,运用Cache-Control来告知浏览器缓存过期的时间间隔而不是时刻,那么即使具体时间不一致,也不影响缓存的管理。
Cache-Control允许的值如下:
no-store
禁止浏览器缓存响应,通常一些非常隐私的数据会启用这个值
no-cache
不允许直接使用本地缓存,必须先发起请求和服务器协商。
max-age=seconds
告知浏览器该响应本地缓存有效的最长期限,以秒为单位。
其他可选值不常见,以后遇到再补充
public: 共有缓存,可被缓存代理服务器缓存,比如CDN
private: 私有缓存,不能被共有缓存代理服务器缓存,可被用户的代理缓存如浏览器。
max-age=[秒]:表示在这个时间范围内缓存是新鲜的无需更新。类似Expires时间,不过这个时间是相对的,而不是绝对的。也就是某次请求成功后多少秒内缓存是新鲜的。
s-maxage=[秒]:类似max-age, 除了仅应用于共享缓存(如代理)。
no-cache:这里不是不缓存的意思,只是每次在使用缓存之前都强制发送请求给源服务器进行验证,检查文件该没改变(其实这里和ETag/Last区别不大)
no-store:就是禁止缓存,不让浏览器保留缓存副本
must-revalidate:告诉浏览器,你这必须再次验证检查信息是否过期, 返回的代号就不是200而是304了。
proxy-revalidate:类似must-revalidate,除了只能应用于代理缓存。
比如,这里我可以设置Cache-Control为:
#Response Headers
Cache-Control:private, max-age=0, must-revalidate
该文件是一个私有文件,只能被浏览器缓存,而不能被代理缓存。max-age标识该缓存立即过期,其实和no-cache实际上区别不大. 然后must-revalidate告诉浏览器,你必须给我再验证文件过没过期,比如接下来可能会验证Last-Modified或者ETag.如果没有过期则使用本地缓存. 其实上面可以直接等同于:
#Response Headers
Cache-Control:private,no-cache
这三个字段主要用来告知浏览器的本地缓存管理策略,优先级为Pragma > Cache-Control > Expires。
协商缓存
当浏览器没有命中本地缓存,如本地缓存过期或者响应中声明不允许直接使用本地缓存,那么浏览器肯定会发起请求。
在http缓存模型中,即使浏览器向服务器发起请求,服务器也不一定要返回整个资源的实体内容。而可以返回协商结果:“浏览器,我的资源没有修改过,你可以直接使用你的本地缓存”。
很显然,服务器要判断浏览器的缓存是否可用,那么必须浏览器告诉服务器一些自己缓存的信息,所以协商缓存相关的header字段,必然是成对出现的。
Last-Modified
格式:Response Headers
: Last-Modified: Tue, 08 Nov 2016 01:50:36 GMT
告诉浏览器资源的最后修改时间,相当于对资源进行了版本管理,至于这个时间怎么生成的,那是服务器的事儿,不在这里讨论。
得知资源的最后修改时间后,客户端会将这个信息提交到服务器做检查,如果服务器验证出最后修改时间是一致的,那么表示该资源没有修改过,可以返回304状态。
浏览器请求头中标记最终修改时间的header字段:
Request Headers
: If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT
ETag
通常情况下,服务器默认是打开Etag的,但是为了防止配置文件不正确,关闭了Etag,这时候,就需要你对对配置文件做一些设置。 这里我以Nginx为例: 打开ngnix.conf文件,检查是否有以下语句:()
etag off;
more_set_headers -s 404 -t 'ETag';
more_clear_headers 'Etag';
即使我没有讨论服务器怎么生成最终修改时间,也可以相见,这个模式会存在不准确的问题:如果资源明明没有改变,但是Last-Modified发生了变化,那么就会返回整个资源实体。
针对这个问题,http1.1还推出了ETag字段,服务器会根据某种计算方式(常见的如md5)给出一个标识符,这个标识符其实标记的是资源的实际内容。
格式:Response Headers
: ETag:"58212f6c-22f23"
检测过程与Last-Modified类似,浏览器请求头中标记ETag的字段:
Request Headers
: If-None-Match:"58212f6c-22f23"
浏览器对缓存的影响
注意观察浏览器行为的开发者很容易发现,输入url访问与f5刷新,各个资源的请求速度好像不太相同。
常见的浏览器会将访问行为分为3种:
-
地址栏输入URL或书签访问
按照正常策略使用缓存 -
按照正常策略使用缓存
按照正常策略使用缓存 -
Ctrl+F5
跳过强缓存与协商缓存,直接加载资源实体
具体的实现方式可以想见的是发送不同的请求头~~
缓存策略的选择
对大多数站点来说,以下内容是非常适合缓存的:
普通不变的图像,如logo,图标等
js、css静态文件
可下载的内容,媒体文件
这些文件很少改变,适合长时间强缓存。
以下内容是做缓存时需要注意的,建议主要使用协商缓存的:
HTML文件
经常替换的图片
经常修改的js、css文件
其中,js、css文件可以通过md5修改文件名的方式改变url来失效缓存,
即在文件内容变化后将main.95d21235.css改为main.1bcbf5de.css,由于url变化,所以不存在缓存的问题。
以下内容从来都不应该使用缓存:
用户隐私等敏感数据
经常改变的api数据接口
其中,后台rest api数据接口的如果需要引入缓存策略,必须要进行比较谨慎的规划,
将频繁改变的接口与基本不变的接口区分,并且在应用服务器中实现Last-Modified/ETag的生成机制以保证缓存不会造成错误的结果。
从这里延伸出去的话,理想情况下,一切网络资源都应该尽可能选择不同策略的缓存,但考虑到开发的成本与难度,这在现实中很难发生,因此应该尝试设置一些明智的缓存策略(最常见的就是给大量的静态图片设置缓存),以在长期缓存和站点改变的需求间达到平衡。
nginx配置缓存策略
-
强缓存
- add_header指令
Syntax: add_header name value [always]; Default: Context: http, server, location, if in location
- expires
Syntax: expires [modified] time; expires epoch | max | off; Default: expires off; Context: http, server, location, if in location
# example: # vim /etc/nginx/sites-available/your_server_config location ~* \.(?:css|js)$ { expires 1d; access_log off; add_header Cache-Control "public"; }
expires为负值时,表示Cache-Control: no-cache; 当为正或者0时,就表示Cache-Control: max-age=指定的时间(秒); 当为max时,会把Expires设置为 “Thu, 31 Dec 2037 23:55:55 GMT”, Cache-Control 设置到 10 年;
通过expires设置过期时间为一天,此时,服务器会根据当前的时间,(此处2018-09-06设置的)加上一天.同时添加Expires和Cache-Control头部标签。 即,得到的Response Header为:
Expires: Fri, 07 Sep 2018 07:09:30 GMT
Cache-Control: max-age=86400 //24*60*60
(HTTP规定,如果出现max-age和expires,则max-age默认覆盖掉expires) 当expires为负数表示no-cache,正数或零表示max-age=time。 如果你不想缓存,可以直接设置:
expires -1; //永远过期,Cache-Control: no-cache
详细可以直接参阅:nginx配置
- 协商缓存
- ETag
Syntax: etag on | off;
Default: etag on;
Context: http, server, location
- Last-Modified
add_header指令,默认开启
vim /etc/nginx/nginx.conf
http {
etag on;
##
# Other Settings
##
}
etag on;
扩展
##设置no-cache
//Nginx
expires -1;
//cache-control
Cache-Control:no-cache
##设置max-age=0
//Nginx
expires 0;
//cache-control
Cache-Control:max-age=0
##设置其他头部
//nginx
add_header Cache-Control "no-cache";
add_header Pragma no-cache;
上面说的基本上是服务器的响应头,那在浏览器的Request headers里存在cache-control代表什么呢? 当请求头中有:Cache-Control: max-age=0,表示缓存需要进行验证(ETag||Last-Modified),如果缓存未过期,则可以使用。 当请求头中有:Cache-Control: no-cache,表示浏览器只能获取最新的文件。 和Response Header中的no-store相对应。
组合拳法之缓存策略
上面介绍的last/ETag/Expires/Cache都是HTTP协议的缓存策略。当然,缓存不止这一种,比如在HTML 4.0中定义的某些meta也可以实现自定义缓存的
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
但,实际情况是,这些meta只能在file:// 本地文件中使用,如果是服务器则默认被覆盖。现在目前主流的就是使用HTTP1.1协议缓存 不过我们一般都不会单独使用某一项。 但是,组合之后他们的效果是怎样的呢?
清除缓存
来源统计之 Referrer 操作
Referrer 头里面会带上本次请求发送的 domain,比如:
Referer: https://now.qq.com/qq/market/index.html
不过,并不是所有的资源都会带上 referrer 头,比如,HTML 资源。默认情况下,HTML 的发送一般会遵循 No Referrer When Downgrade 的策略,即仅当发生协议降级(如 HTTPS 页面引入 HTTP 资源,从 HTTPS 页面跳到 HTTP 等)时不发送 Referrer 信息。。而这个策略的定义,主要根据 referrerPolicy 这个请求头来定制。其基本属性值为:
no-referrer: 不发送 referrer 头。
no-referrer-when-downgrade[default]: 当发生协议降级时采用,(如 HTTPS 页面引入 HTTP 资源,从 HTTPS 页面跳到 HTTP 等)。
origin: 值发送 host
origin-when-crossorigin: 在跨域的时候只发送 host,其余是完整 url。
unsafe-url: 都发送 referrer 信息。
referrerPolicy 内容设置可以通过 Response 中的 Referrer-Policy 来确定。它可以手动指定 referrer 策略。基本内容为:
Referrer-Policy: referrer no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|unsafe-url;
不过,在发送 HTML 的时候并不保证会遵循这个策略,应该 HTML 文档的发起 是 没有任何响应头来做参考的。所以,可以通过 HTML 文档中的 meta 标签来指定发送的请求头
<meta name="referrer" content="no-referrer|no-referrer-when-downgrade|origin|origin-when-crossorigin|unsafe-url">
不过,上述的设置对于纯粹从浏览器输出网址发送 HTML 请求的情况都是无效的。其默认都是 no-referrer-when-downgrade 策略。
网友评论