vlambda博客
学习文章列表

浏览器缓存机制与Nginx配置调优

缓存可以说是性能优化中简单高效的一种优化方式了, 一个优秀的缓存策略可以缩短网页请求资源的距离, 减少延迟, 并且由于缓存文件可以重复利用, 还可以减少带宽, 降低网络负荷. 对于浏览器缓存, 相信大家对它真的是又爱又恨. 一方面极大地提升了用户体验, 而另一方面有时会因为读取了缓存而展示了已经过期的数据, 而在开发过程中千方百计地想把缓存禁掉, 所以接下来本文就对浏览器的缓存机制进行详细的讲解, 彻底掌握浏览器的缓存规则, 妈妈再也不用担心缓存问题啦~

HTTP状态码

200 (form memory cache) 不请求网络资源,从内存中读取资源,一般脚本、字体、图片会存在内存当中

200 (form disk cache) 不请求网络资源,从磁盘中读取资源,一般非脚本会存在内存当中,如css等

200 (1kb) 从服务器下载最新资源. (1kb即资源的大小)

304 (1kb) 请求服务端发现资源没有更新,使用本地资源. (1kb即报文的大小)

以上是chrome在请求资源是最常见的几种http状态码

前两种200状态码是不需要浏览器请求服务器的, 直接从本地读取资源, 所以速度是最快的, 而304状态码也是缓存, 只不过需要浏览器先请求服务器, 这种是协商缓存

那么浏览器究竟在什么情景下会返回以上几种不同的状态码呢?

答案很简单, 在浏览器第一次访问某个URL时, 服务端会直接返回第三种200状态码, 返回内容是被请求的最新资源, 与此同时服务端可以在返回内容时主动设置Response Headers里的last-modified属性标记此资源在服务端最后被修改的时间, 还可以设置cache-control属性etag属性以及expire属性, 浏览器就会根据返回的几个属性, 自行判断下次该如何请求资源, 那么下文将依次讲解这些属性的作用, 以及在什么情况会返回剩余的三种状态码.

last-modified

last-modified格式类似这样:

last-modified : Fri , 12 May 2006 18:53:33 GMT

客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器发送if-modified-since报头,询问该时间之后文件是否有被修改过:

if-modified-since : Fri , 12 May 2006 18:53:33 GMT

如果服务器端的资源没有变化,则返回 304(Not Changed)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

Nginx默认开启last modified特性, 也可以在nginx.conf中添加以下配置, 主动控制:

 
   
   
 
if_modified_since off|on;

etag

HTTP协议规格说明定义etag为“被请求变量的实体值”。另一种说法是,etag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个HTML文档,但也可能是JSON或XML文档。

服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:

etag:“50b1c1d4f775c61:df3”

客户端的查询更新格式是这样的:

if-none-match : W / “50b1c1d4f775c61:df3”

如果etag的值没有发生改变,则返回 304(Not Changed)状态码,这也和last-modified一样。etag在断点下载时会非常有用。

Nginx1.7.3及以上的版本默认开启Etag特性, 但是和Last-Modified一样, 可以主动配置:

 
   
   
 
etag off|on;

cache-control

cache-control 是最重要的规则。这个字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令。这些指令指定用于阻止缓存对请求或响应造成不利干扰的行为。这些指令通常覆盖默认缓存算法。缓存指令是单向的,即请求中存在一个指令并不意味着响应中将存在同一个指令。

简单来说, 当同时存在last-modified以及etag还有cache-control这个3个属性时, cache-control的优先级是最高的

cache-control有以下几个值:

public 所有内容都将被缓存

private 内容只缓存到私有缓存中

no-cache 所有内容都不会被缓存

no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中

must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证

max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效, 如果和Last-Modified一起使用时, 优先级更高

浏览器对以上几种值的不同表现:

打开新窗口

public 会从缓存中读取资源, 也就是返回前两种200状态码

private, no-cache, no-store 都会重新访问服务器。

如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,例如:

cache-control: max-age=5(表示当访问此网页后的5秒内再次访问不会去服务器)

在原窗口按Enter键

public 会从缓存中读取资源, 也就是返回前两种200状态码

private或must-revalidate 则只有第一次访问时会访问服务器,以后就不再访问。

no-cache, no-store 每次都会访问。

如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器

点击刷新按钮

无论将cache-control设置为什么值,浏览器都会重新访问服务器

点击后退按钮

publicprivatemust-revalidatemax-age 都不会重新访问

no-cache 则每次都重复访问

cache-control是关于浏览器缓存的最重要的设置,因为它覆盖其他设置,比如 expires 和 last-modified。另外,由于浏览器的行为基本相同,这个属性是处理跨浏览器缓存问题的最有效的方法。

Nginx设置cache-control(例如设置max-age为30秒):

 
   
   
 
add_header Cache-Control "max-age=30";

expires

Expires 头部字段提供一个日期和时间,响应在该日期和时间后被认为失效。失效的缓存条目通常不会被缓存(无论是代理缓存还是用户代理缓存)返回,除非首先通过原始服务器(或者拥有该实体的最新副本的中介缓存)验证。(注意:cache-control max-age 和 s-maxage 将覆盖 Expires 头部。)

Expires 属性接收以下格式的值:

Expires: Sun, 08 Nov 2009 03:37:26 GMT

如果查看内容时的日期在给定的日期之前,则认为该内容没有失效并从缓存中提取出来。反之,则认为该内容失效,缓存将采取一些措施。

以上几种属性的优先级

last-modifiedetagcache-controlexpires 被服务端同时设置时, 浏览器的下次请求会做出如下判断:

cache-control 与 expires 控制浏览器是否从本地读取缓存, 并且cache-control会重写expires的规则。

例如cache-control设置了max-age, 浏览器则会先判断max-age,

如果cache-control没有设置max-age, 浏览器则会先判断expire, 再发送 Http 请求

服务器收到请求则会先判断资源的 last-modified,再判断 etag ,必须都没有过期,才能返回 304(Not Changed)状态码

可能出现的问题

前面只讲了几种属性同时被设置后, 浏览器和服务器判断的优先级, 还有一种情况(也是最常见的情况)也是需要我们去考虑的

那就是服务端只设置了last-modified 以及 etag, 并没有设置cache-controlexpires属性

为什么说这种情况是最常见的? 仔细想想还真是那么回事, 因为nginx默认开启了last-modified和etag属性

如果一只后端汪正常地配置了生产环境, 并没有在缓存方面做一些配置, 那默认就是这种情况

这种情况下浏览器就不得不自行判断它应该将资源缓存多久。有些浏览器会将其缓存一天以上。

Google caching best practices guide 里提到: 浏览器会根据 last-modified 自行推算缓存时长。

Firefox 的推算方法是:

缓存时长 = (Date - last-modified) / 10

Chrome / Safari / IE 并没有公布他们的公式或算法。

此类文件的缓存时长通常取决于以下因素:

  • 浏览器开辟的缓存空间大小

  • 用户浏览过的站点数量和大小

  • 用户是否关闭了浏览器

也就是说当服务端只配置了last-modified 以及 etag, 没有配置cache-control和expires的时候, 除非用户手动点击刷新按钮, 否则浏览器每次都会从本地缓存中加载, 而且这个本地缓存的缓存时间也是未知的

这就导致了一个问题, 比如说你没有做任何缓存的配置, 却发现每次更新了网站的PHP, HTML, JS, CSS文件, 非得让用户手动点击刷新按钮才能显示更新内容, 这可不是我们想要的!(别问我怎么知道的)

所以,你如果不想关闭 last-modifiedetag, 还不想浏览器从本地加载缓存资源,就应该显示地指定cache-controlexpires

下面是我网站的nginx的缓存配置(仅供参考):

 
   
   
 
#设置css/js/图片等静态资源的expires为30天
location ~ .*\.(css|js|ico|png|gif|jpg|json|mp3|mp4|flv|swf)(.*) {
expires 30d;
}

#设置index.html的max-age为30秒
location /index.html {
add_header Cache-Control "max-age=30";
}