浏览器缓存概述
浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下:
- 先根据这个资源的一些http header判断它是否命中强缓存(后文再解释什么是强缓存),如果命中(cache hit),则直接从本地获取缓存资源,不会发请求到服务器;
- 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些request header验证这个资源是否命中协商缓存(后文再解释什么是协商缓存),称为http再验证(revalidation),如果命中(revalidate hit再验证命中),服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;
- 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源;区别是,强缓存不对发送请求到服务器,但协商缓存会。
- 当协商缓存也没命中时,服务器就会将资源发送回客户端。
浏览器缓存分类
- 强缓存
直白点的方式描述下:客户端第一次问服务器要某个资源时,服务器丢还给客户端所请求的这个资源同时,告诉客户端将这个资源保存在本地,并且在未来的某个时点之前如果还需要这个资源,直接从本地获取就行了,不用向服务器请求。这种方式缓存下来的资源称为强缓存。
强缓存利用http的返回头部的中Expires(实体首部字段)或者Cache-Control(通用首部字段)两个字段来控制的,用来表示资源的缓存时间。服务器通过这两个首部字段告知客户端资源的缓存过期时间和缓存最大生命周期。客户端得知资源的缓存过期时间和最大生命周期后,即可自行判断是否可不建立与服务器的链接,直接从浏览器缓存中获取资源。
命中强缓存时,浏览器同样会受到status=200的response,chrome中可通过size区分从服务器返回的资源还是强缓存获得的资源。
- Expires
该字段是http1.0时的规范,值为一个绝对时间的GMT格式的时间字符串,代表缓存资源的过期时间,在这个时点之前,即命中缓存。其过程如下:
-
浏览器第一次跟服务器请求一个资源时,服务器在返回这个资源时,在相应头部会加上Expires,如图:
- 浏览器接收到该资源后,会把这个资源连同response header一起缓存下来;
- 浏览器再次请求这个资源时,会先从缓存中找到这个资源,然后获取Expires时间与当前的请求时间比较,如果Expires时间比当前浏览器的请求时间晚,说明缓存未过期,即命中缓存;
- 如果当前请求时间比Expires晚,说明缓存过期,即未命中缓存,浏览器就会发送请求到服务器申请获取资源。
缺点:服务器返回的Expires时点是服务器上的时间,可能与客户端有时间差,时间差太大时可能造成缓存混乱。
Cache-Control:max-age
该字段是http1.1的规范,强缓存利用其max-age值来判断缓存资源的最大生命周期,它的值单位为秒,Cache-Control : max-age=3600代表缓存资源有效时间为1小时,即从第一次获取该资源起一小时内的请求都被认为可命中强缓存。其过程如下:
-
浏览器第一次跟服务器请求一个资源时,服务器在返回这个资源时,在相应头部会加上Cache-Control:max-age=XXXXXXXX,如图:
浏览器接收到资源后,连同response header一起缓存下来;
- 浏览器再次跟服务器请求同一个资源时,会先从缓存中找到这个资源,获取date(第一次资源返回的响应时间)和max-age并计算出一个有效期(date + max-age),然后再和浏览器请求时间比较最后判断是否命中缓存(逻辑同Expires);
- 如果没有命中缓存,浏览器直接从服务器请求资源,Cache-Control会在重新获取到服务器返回资源时更新。
Cache-Control描述的是相对时间,采用本地时间来计算资源的有效期,所以相比Expires更可靠。
这两个Header可以只用其一,也可以一起使用。一起使用时以Cache-Control为准。
- 协商缓存
直白点的方式描述下:客户端第一次问服务器要某个资源时,服务器丢还给客户端所请求的这个资源同时,将该资源的一些信息(文件摘要、或者最后修改时间)也返回给客户端,告诉客户端将这个资源缓存在本地。当客户端下一次需要这个资源时,将请求以及相关信息(文件摘要、或者最后修改时间)一并发送给服务器,由服务器来判断客户端缓存的资源是否需要更新:如不需要更新,就直接告诉客户端获取本地缓存资源;如需要更新,则将最新的资源连同相应的信息一并返回给客户端。
当强缓存未命中时,浏览器就会发送请求到服务器,服务器会验证协商缓存是否命中,如果协商缓存命中,请求返回的http状态为304,并会显示说明Not Modified,浏览器收到该返回后,就会从缓存中加载了。
协商缓存利用[Last-Modified , If-Modified-Since] 和 [ETag , If-None-Match]这两对Header来管理。
(1)Last-Modified & If-Modified-Since
Last-Modified为实体首部字段,值为资源最后更新时间,随服务器response返回。
If-Modified-Since为请求首部字段,通过比较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存,浏览器从缓存中获取资源;如果有过修改,则服务器返回资源,同时返回新的Last-Modified时间。其过程如下:
- 浏览器第一次请求服务器资源,且服务器返回了该资源时,会在response headers中加上Last-Modified,这个header表示这个资源在服务器上的最后一次修改时间;
- 当浏览器再次请求该资源时,会在request headers中加上If-Modified-Since,这个值即为上一次服务器返回的Last-Modified时间;
- 服务器再次收到资源请求时,将If-Modified-Since时间和资源在服务器上的最后修改时间与对比,如果If-Modifid-Since与最后修改时间一致,则命中缓存,服务器返回304,浏览器从缓存中获取资源;若未命中缓存,服务器返回资源同时,浏览器缓存资源的Last-Modified会被更新。
(2)ETag & If-None-Match
有些情况下仅判断最后修改日期来验证资源是否有改动是不够的:
- 存在周期性重写某些资源,但资源实际包含的内容并无变化;
- 被修改的信息并不重要,如注释等;
- Last-Modified无法精确到毫秒,但有些资源更新频率有时会小于一秒。
为解决这些问题,http允许用户对资源打上标签(ETag)来区分两个相同路径获取的资源内容是否一致。通常会采用MD5等密码散列函数对资源编码得到标签(强验证器);或者通过版本号等方式,如W/”v1.0”(W/表示弱验证器)。
ETag为相应头部字段,表示资源内容的唯一标识,随服务器response返回;
If-None-Match为请求头部字段,服务器通过比较请求头部的If-None-Match与当前资源的ETag是否一致来判断资源是否在两次请求之间有过修改,如果没有修改,则命中协商缓存,浏览器从缓存中获取资源;如果有过修改,则服务器返回资源,同时返回新的ETag。其过程如下:
- 服务器第一次收到浏览器发出的资源请求时,会在response headers中加上ETag,这个ETag是根据该资源生成的唯一标识,这个唯一标识是个字符串,只要服务器认为资源有变化且应该提供新的资源,则ETag就必须有变化。浏览器将资源连同ETag一并缓存。
- 当浏览器再一次向服务器发送该资源的请求时,会在request headers中加上If-None-Match,该值即为第一次服务器返回的ETag值;
- 服务器收到资源请求后,会根据要请求的资源重新计算生成相应的ETag,然后与If-None-Match比较。对比结果一致即命中缓存,不一致则未命中缓存,返回资源同时将新的ETag发送至浏览器。
(3)协商缓存管理
[Last-Modified , If-Modified-Since]和[ETag , If-None-Match]一般同时启用,这是为了处理Last-Modified不可靠的情况。
其他注意事项
- 分布式系统里多台服务器间的文件的Last-Modified必须保持一致,以免负载均衡到不同服务器导致对比结果不一致;
- 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样,淘宝页面中的静态资源response headers中都没有ETag)。