缓存分很多种:服务器缓存,第三方缓存,浏览器缓存等。其中浏览器缓存是代价最小的,因为浏览器缓存依赖的是客户端,而几乎不耗费服务器端的资源。让浏览器做缓存需要给浏览器发送指定的Http头,告诉浏览器缓存多长时间,或者坚决不要缓存。
浏览器缓存分类
浏览器缓存分为强缓存和协商缓存。浏览器加载一个页面的简单流程如下:
- 浏览器先根据这个资源的http头信息来判断是否命中强缓存。如果命中则直接加载缓存中的资源,并不会将请求发送到服务器。
- 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。服务器来判断浏览器本地缓存是否失效。若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。
- 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。
强缓存
命中强缓存时,浏览器并不会将请求发送给服务器。在Chrome的开发者工具中看到http的返回码是200,但是在Size列会显示为(from cache)。强缓存是利用http的返回头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间。
协商缓存
若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modify/If-Modify-Since或Etag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,浏览器从缓存中加载资源。
区别
获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|
强缓存 | 200(from cache) | 直接从缓存取 |
协商缓存 | 304(Not Modified) | 通过服务器来告知缓存是否可用 |
状态码304
状态码304是什么意思呢?这里就涉及到浏览器的缓存机制啦,浏览器在第一次访问页面时下载的资源会缓存起来,第二次访问时会判断在缓存中是否已有该资源并且有没有更新过,如果已有该资源且没有更新过,则去缓存去取,这样减少了下载资源的时间。
Last-Modified/If-Modified-Since
原理
通过HTTP Request Header中的if-modified-since
和Response Headers中的last-modified
来实现。HTTP请求把if-modified-since
的时间传给服务端,服务端把last-modified
时间与之对比,如果相同,则意味着文件没有改动,则返回304,浏览器则从缓存中获取资源,无需下载。如果你第二次(或第三次,或第四次)请求相同的数据,你可以告诉服务器你上一次获得的最后修改日期:在你的请求中发送一个If-Modified-Since
头信息,它包含了上一次从服务器连同数据所获得的日期。如果数据从那时起没有改变,服务器将返回一个特殊的 HTTP状态代码304,这意味着“从上一次请求后这个数据没有改变”。这一点有何进步呢?当服务器发送状态编码304时,不再重新发送数据。您仅仅获得了这个状态代码。所以当数据没有更新时,你不需要一次又一次地下载相同的数据;服务器假定你有本地的缓存数据。
所有现代的浏览器都支持最近修改(last-modified)的数据检查。如果你曾经访问过某页,一天后重新访问相同的页时发现它没有变化,并奇怪第二次访问时页面加载得如此之快——这就是原因所在。
虽然这种方法减少了已缓存资源的下载时间,但是仍然发起了一次http请求。那么如何免去这次HTTP请求呢,这就需要用到HTTP的Expires
和Cache-Control
啦。
但是Last-Modified/If-Modified-Since出现了如下问题,于是出现了ETag/If-None-Match。
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since 能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
ETag/If-None-Match
Etag/If-None-Match返回的是一个校验码(ETag:entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。
ETag是实现与最近修改数据检查同样的功能的另一种方法:没有变化时不重新下载数据。其工作方式是:服务器发送你所请求的数据的同时,发送某种数据的hash(在ETag头信息中给出)。hash 的确定完全取决于服务器。当第二次请求相同的数据时,你需要在If-None-Match
:头信息中包含ETag hash,如果数据没有改变,服务器将返回304状态代码。与最近修改数据检查相同,服务器仅仅发送304状态代码;第二次将不为你发送相同的数据。在第二次请求时,通过包含ETag hash,你告诉服务器:如果hash 仍旧匹配就没有必要重新发送相同的数据,因为你还有上一次访问过的数据。
这是另一种维度上确定Cache是否需要更新,服务端会返回响应的Etag,这个Etag客户端不需要关心其具体是怎么实现的,只是要能够记录下这个值就行。服务端通过对内容进行hash或者别的算法来生成这样的ETag,当客户端请求的时候,只需要去检查两者是否相同,即可知道内容有没有发生改变变化。返会的当时与Last-Modified/If-Modified-Since相同。
ETag扩展
我们对ETag寄予厚望,希望它对于每一个url生成唯一的值,资源变化时ETag也发生变化。神秘的Etag是如何生成的呢?以Apache为例,ETag生成靠以下几种因子
- 文件的i-node编号,此i-node非彼iNode。是Linux/Unix用来识别文件的编号。是的,识别文件用的不是文件名。使用命令’ls –I’可以看到。
- 文件最后修改时间
- 文件大小生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。所以,理论上ETag也是会重复的,只是概率小到可以忽略。
Expires
Expires(过期时间):HTTP头信息Expires属性是HTTP控制缓存的基本手段,在HTTP1.0就被提出,这个属性告诉缓存器:相关副本在多长时间内是新鲜的。过了这个时间,缓存器就会向源服务器发送请求,检查文档是否被修 改。几乎所有的缓存服务器都支持Expires属性;
Expires头信息:对于设置静态图片文件可缓存特别有用;因为这些图片修改很少,你可以给它们设置一个特别长的过期时间,这会使你的网站对用户变得相应非常快;他们对于控制有规律改变的网页也很有用。
过期时间头信息属性值只能是HTTP格式的日期时间,记住:HTTP的日期时间必须是格林威治时间(GMT),而不是本地时间。所以使用过期时间属性一定要确认你的Web服务器时间设置正确,一个途径是通过网络时间同步协议(Network Time Protocol NTP)。
即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱。于是发展出了Cache-Control。
Cache-Control
Cache-Control(缓存控制):这是HTTP 1.1介绍的另外一组头信息属性:Cache-Control响应头信息,让网站的发布者可以更全面的控制他们的内容,并定位过期时间的限制。
Cache-Control是一个相对时间,例如Cache-Control:3600,代表着资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。
常用的Cache-Control响应头信息包括
- max-age=[秒] — 执行缓存被认为是最新的最长时间。类似于过期时间,这个参数是基于请求时间的相对时间间隔,而不是绝对过期时间,[秒]是一个数字,单位是秒:从请求时间开始到过期时间之间的秒数。
- s-maxage=[秒] — 类似于max-age属性,除了他应用于共享(如:代理服务器)缓存
- public — 标记认证内容也可以被缓存,一般来说: 经过HTTP认证才能访问的内容,输出是自动不可以缓存的;可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
- private - 只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存
- no-cache — 强制每次请求直接发送给源服务器,而不经过本地缓存版本的校验。不使用本地缓存。需要使用缓存协商。
- no-store — 强制缓存在任何情况下都不要保留任何副本,直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
- must-revalidate — 告诉缓存必须遵循所有你给予副本的新鲜度的,HTTP允许缓存在某些特定情况下返回过期数据,指定了这个属性,你高速缓存,你希望严格的遵循你的规则。
- proxy-revalidate — 和 must-revalidate类似,除了他只对缓存代理服务器起作用
例子:Cache-Control:max-age=3600, must-revalidate
另外,通过指定“Expires”值也会影响到缓存。例如,指定Expires值为一个早已过去的时间,那么访问此网时若重复在地址栏按回车,那么每次都会重复访问
给静态资源(HTML文件,图片文件等的Repsone加上Expires/Cache-Control Header是很有效的一招。区别就是
- Expires的值只能是一个固定日期,比如“Thu 27 Nov 2008 07:00:00 GMT”,不能是一个类似“从现在开始之后10年”这样一个随机浮动的值。
- 如果要这样的效果,可以用Cache-Control这样的Header,如果HTTP Response中有这样的Header:“Cache-Control: max-age = 100”,表示这个资源在cache中的最大寿命是100秒。
其实就应该给Expires设一个永远不会过期的时间,比如你现在有一个文件叫logo.gif,需要用一个新的logo的时候,你不要去 覆盖原来的文件,而把新的logo存成logo_v2.gif,让相关网页引用新的logo_v2.gif,这样可以让新老网页同时工作,实在犯不上为了节省存储空间覆盖原有文件。
总结
标签
这几个http头可以作为meta标签发送到客户端,但是需要注意的是Http头中的设置优先级更高一些,例如
<meta http-equiv="Expires" CONTENT="Fri, 30 Oct 1998 14:19:41">
<meta http-equiv="Cache-Control" CONTENT="no-cache">
这样写的话仅对该网页有效,对网页中的图片或其他请求无效,并不会做任何cache。
区别
- Expires/Cache-Control Header是控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只是Cache-Control比Expires可以控制的多一些, 而且Cache-Control会重写Expires的规则。
- Last-Modified/If-Modified-Since和ETag/If-None-Match是浏览器发送请求到服务器后判断文件是否已经修改过,如果没有修改过就只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器。属于一种协商缓存的方式。
已经被浏览器缓存的,如何更新或废弃?
可以在资源的内容更改后,更改资源的网址,强制用户下载新响应。比如加上时间戳或者版本号
浏览器缓存机制的设置注意事项
- 保证服务器提供验证码 (ETag)
- 确定代理缓存可以缓存哪些资源:对所有用户的响应完全相同的资源很适合由CDN或其他代理缓存进行缓存。
- 定每个资源的最优缓存周期:不同的资源可能有不同的更新要求。审查并确定每个资源适合的max-age。
- 动最小化:有些资源的更新比其他资源频繁。如果资源的特定部分(例如JavaScript函数或一组CSS样式)会经常更新,应考虑将其代码作为单独的文件提供。这样,每次获取更新时,剩余内容(例如不会频繁更新的库代码)可以从缓存中获取,确保下载的内容量最少。
用户行为对缓存的影响
Cache-control的具体效果和你的浏览方式有关:
- 打开新窗口:
- 值为private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。
- 而如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器。
- 在地址栏回车
- 值为private或must-revalidate则只有第一次访问时会访问服务器,以后就不再访问。
- 值为no-cache,那么每次都会访问。
- 值为max-age,则在过期之前不会重复访问。
- 按后退按扭
- 值为private、must-revalidate、max-age,则不会重访问,
- 值为no-cache,则每次都重复访问
- 按刷新按扭
- 无论为何值,都会重复访问
表格形式:
用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5/按钮刷新 | 无效(BR重置max-age=0) | 有效 |
Ctrl+F5刷新 | 无效(重置CC=no-cache) | 无效(请求头丢弃该选项 ) |
实用性与缺陷
待研究!