http缓存机制及nginx相关配置
浏览器http缓存,既是网页静态资源服务性能优化的一把利器,也是无数web开发者在工作之初谈之色变的一大难题。
在开发过程中我们极力避免缓存,但在生产环境中,我们又在想尽办法利用缓存。
所以了解浏览器缓存的机制,是一个优秀开发者绕不开的重要基础知识。
各种缓存的命中与否,说到底不过是几个与之相关的http header数据的匹配与校验。如果了解了每个相关header的意义与关系,那么就能将缓存策略运用自如。
缓存分类
在浏览器的缓存模型中,一次成功(能拿到想要的数据)的请求,会有以上三种情况。
200 from cache
直接从本地缓存(有的文章称之为强缓存)中获取响应,返回200状态,chrome网络面板中的size项显示
(from cache)
。
最快速,最省流量,因为根本没有向服务器发送请求。304 Not Modified
在本地缓存没有命中的情况下,请求头中发送一定的校验数据到服务端,校验成功后服务器返回304 not modified表示资源未被修改,浏览器从本地缓存中获取响应,这种缓存方式通常被称为协商缓存。
快速,发送的数据很少,只返回一些基本的响应头信息,数据量很小,不发送实际响应体。200 OK
以上两种缓存全都失败,服务器返回完整响应。
没有用到缓存,相对最慢。
本地缓存
本地缓存的使用,相当于基于之前服务器的response header,浏览器认为当前的缓存可以直接使用,于是不再与服务器进行任何交互而直接使用缓存的过程。
与本地缓存命中相关的http header:
Pragma
这个是http1.0时代的遗留产物,该字段被设置为no-cache时(实际上现有的RFC标准标明只有这个可选值),会告知浏览器禁用本地缓存,即每次都向服务器发送请求。
Expires
http1.0时代用来启用本地缓存的字段,expires值对应一个形如
Thu, 31 Dec 2037 23:55:55 GMT
的格林威治时间,告诉浏览器缓存实现的时刻,如果还没到该时刻,标明缓存有效,无需发送请求。但是这个方式有个很明显的问题,就是浏览器与服务器的时间是不能保证一致的,如果时间差距较大,那么会影响缓存管理的结果。
Cache-Control
http1.1针对Expires时间不一致的问题,采取了一个十分聪明的设定,运用Cache-Control来告知浏览器缓存过期的时间间隔而不是时刻,那么即使具体时间不一致,也不影响缓存的管理。
Cache-Control允许的值如下:
no-store
禁止浏览器缓存响应,通常一些非常隐私的数据会启用这个值
no-cache
不允许直接使用本地缓存,必须先发起请求和服务器协商。
max-age=delta-seconds
告知浏览器该响应本地缓存有效的最长期限,以秒为单位。
其他可选值不常见,以后遇到再补充
这三个字段主要用来告知浏览器的本地缓存管理策略,优先级为Pragma > Cache-Control > Expires。
协商缓存
当浏览器没有命中本地缓存,如本地缓存过期或者响应中声明不允许直接使用本地缓存,那么浏览器肯定会发起请求。
在http缓存模型中,即使浏览器向服务器发起请求,服务器也不一定要返回整个资源的实体内容。而可以返回协商结果:“浏览器,我的资源没有修改过,你可以直接使用你的本地缓存”。
很显然,服务器要判断浏览器的缓存是否可用,那么必须浏览器告诉服务器一些自己缓存的信息,所以协商缓存相关的header字段,必然是成对出现的。
Last-Modified
格式:
Last-Modified: Tue, 08 Nov 2016 01:50:36 GMT
告诉浏览器资源的最后修改时间,相当于对资源进行了版本管理,至于这个时间怎么生成的,那是服务器的事儿,不在这里讨论。
得知资源的最后修改时间后,客户端会将这个信息提交到服务器做检查,如果服务器验证出最后修改时间是一致的,那么表示该资源没有修改过,可以返回304状态。
浏览器请求头中标记最终修改时间的header字段:
If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT
ETag
即使我没有讨论服务器怎么生成最终修改时间,也可以相见,这个模式会存在不准确的问题:如果资源明明没有改变,但是Last-Modified发生了变化,那么就会返回整个资源实体。
针对这个问题,http1.1还推出了ETag字段,服务器会根据某种计算方式(常见的如md5)给出一个标识符,这个标识符其实标记的是资源的实际内容。格式:
ETag:"58212f6c-22f23"
检测过程与Last-Modified类似,浏览器请求头中标记ETag的字段:
If-None-Match:"58212f6c-22f23"
浏览器行为对缓存的影响
注意观察浏览器行为的开发者很容易发现,输入url访问与f5刷新,各个资源的请求速度好像不太相同。
常见的浏览器会将访问行为分为3种:
地址栏输入URL或书签访问
按照正常策略使用缓存
F5刷新
跳过强缓存,但是会使用协商缓存
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指令
1
2
3Syntax: add_header name value [always];
Default: —
Context: http, server, location, if in location给状态码2,3开头的响应添加响应头,如Pragma/Expires/Cache-Control,可以继承。
expires指令
1
2
3
4
5Syntax: expires [modified] time;
expires epoch | max | off;
Default:
expires off;
Context: http, server, location, if in locationexpires为负值时,表示Cache-Control: no-cache;
当为正或者0时,就表示Cache-Control: max-age=指定的时间(秒);
当为max时,会把Expires设置为 “Thu, 31 Dec 2037 23:55:55 GMT”, Cache-Control 设置到 10 年;
协商缓存相关配置
ETag
1
2
3
4Syntax: etag on | off;
Default: etag on;
Context: http, server, locationLast-Modified
add_header指令,默认开启