愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

浏览器缓存技术总结

一、浏览器缓存技术

浏览器中,为了加快一些不常变内容的访问速度,会将其缓存在本地中,当下次再次访问时,就直接加载这些资源,从而加速访问。这种技术称之为浏览器缓存。
判断浏览器加载的一个资源,是否是使用缓存,可以使用开发者工具查看Network,存在缓存中的资源,其Size会显示为from disk cache或者from memory cache,如:

启用缓存后,至少会带来两点好处:
(1)减少页面等待时间
(2)减少服务器负载
而浏览器缓存的启用和如何缓存,这个行为是 由服务器控制的,也就是说服务端返回的响应报文中的响应头,决定了如何进行浏览器缓存,在HTTP协议中,和浏览器缓存有关的字段有:

  • 通用首部字段
    • Cache-Control 控制缓存的行为
    • Pragma 当值为no-cache时,禁用缓存
  • 请求首部字段
    • If-Match 比较Etag是否一致
    • If-None-Match 比较Etag是否不一致
    • If-Modified-Since 比较资源最后更新的时间是否一致
    • If-Unmodified-Since 比较资源最后更新的时间是否不一致
  • 响应首部字段
    • Etag 资源的匹配信息
  • 实体首部字段
    • Expires 实体主体过期的时间
    • Last-Modified 资源最后一次修改的时间


二、强缓存与协商缓存

浏览器缓存按缓存策略的不同,可以分为强缓存协商缓存。他们的概念是:
1)浏览器根据资源的HTTP Header判断资源是否命中强缓存,如果命中,则 直接从缓存中读取资源,连请求都不会发送到服务器
2)如果未命中强缓存,则浏览器会发送资源请求到服务器,根据资源的HTTP Header判断是否命中协商缓存,如果命中协商缓存,则服务器会将请求返回,但是 不返回资源的数据,而是告诉客户端可以从缓存中加载资源,从而让客户端自行从缓存中加载资源
也就是说,强缓存协商缓存的不同垫在于,协商缓存会发送请求到服务器判断是否命中缓存,而它们的共同点则在于一旦命中缓存,便都从客户端缓存中加载资源。如果协商缓存也没有命中,则使用服务端返回的资源数据

1、强缓存:Expires和Cache-Control

当某个资源命中强缓存时,其返回的HTTP状态为200,Network里的Size中会显示为from cache,强缓存是通过HTTP Header的ExpiresCache-Control控制的。
其中,Expires 表示资源过期的绝对时间,以GMT格式字符串表示,如:Mon, 28 Aug 2028 16:37:42 GMT,浏览器处理该字段的过程如下:
1)浏览器第一次请求服务器某个资源,服务器响应并返回该资源,响应头中会带上Expires字段
2)浏览器缓存下该资源和 其响应头故缓存命中的请求中的header是来自于之前缓存的header
3)当浏览器再次请求该资源时,就先从缓存中查找,若找到资源,就比较Expires和当前请求时间,若currentTime < Expires,返回资源,否则请求服务器加载资源并更新缓存

不过Expires是比较老的强缓存管理,服务器返回的是绝对的时间,故如果客户端与服务端时间上不同步,缓存管理就可能出现问题(如随便修改下客户端时间,就能够影响缓存有效与否的判断)。故HTTP/1.1里引入了Cache-Control,当Cache-ControlExpires同时存在时,优先级为Cache-Control更高。不过Expires是比较老的强缓存管理,服务器返回的是绝对的时间,故如果客户端与服务端时间上不同步,缓存管理就可能出现问题(如随便修改下客户端时间,就能够影响缓存有效与否的判断)。故HTTP/1.1里引入了Cache-Control,当Cache-ControlExpires同时存在时,优先级为Cache-Control更高。
然而Cache-Control并不是响应头的专利,实际上request和response里都可以有Cache-Control字段,但request的Cache-Control字段,则主要是提供一套 客户端主动控制缓存代理服务器的缓存策略 方案,但这就不属于浏览器缓存的范畴了,所以我们这里讨论的Cache-Control,则主要是从响应头角度来讨论。Cache-Control的常用取值如下:

  • max-age=[秒],表示指定[秒]内,缓存有效,客户端可以直接使用缓存
  • no-cache,告诉浏览器、缓存服务器,不管本地副本是否过期,使用副本之前,一定要到源服务器进行副本有效性校验
  • no-store,真正的不进行缓存,每次必须重新请求服务器
  • must-revalidate,告诉浏览器、缓存服务器,本地副本过期之前可以使用本地副本;本地副本一旦过期,则必须去源服务器进行有效性校验
  • public,表示响应可被任何中间人(如:中间代理、CDN)缓存
  • private,表示响应是专于某单个用户的,中间人不能缓存此响应,只能应用于浏览器的私有缓存中

2、协商缓存:Last-Modified和Etag

当某个资源未命中强缓存时,就会发一个请求到服务器验证协商缓存是否命中。协商缓存如果命中,就会返回304 Not Modified的HTTP响应报文,然后浏览器便从本地加载缓存。
与协商缓存有关的方式有两种,即为Last-ModifiedEtag

Last-Modified控制缓存过程

1)浏览器第一次跟服务器请求某个资源,服务器在返回资源的同时,头部还带上Last-Modified,表示该资源在服务器上的最后修改时间
2)浏览器再次请求资源时,在请求头里带上If-Modified-Since,值为上一次请求返回的Last-Modified
3)服务端根据浏览器传过来的If-Modified-Since和资源在服务器上最后的修改时间 判断资源是否有变化,如果没有变化则返回304 Not Modified,并且304报文的头部里不会加上Last-Modified。如果有变化,那么返回新的资源,并且头部里带上Last-Modified
4)对于304的响应,浏览器就从缓存中加载资源
相比Expires,虽然使用Last-Modified的协商缓存也是采用时间对比形式,但是Last-Modified全程使用的是服务端时间,而非客户端时间,故要可靠一些。但是有时候也可能出现:服务端上的资源其实有变化,但是最后修改时间却没有变化的情况,也就是缓存不够精准的情况。为了解决这种问题,出现了ETagIf-None-Match这对CP。

ETag控制缓存过程

1)浏览器第一次跟服务器请求某个资源,服务器在返回这个资源的同时,头部会带上ETag,ETag的值是根据当前请求资源生成的唯一标识,这个唯一标识是一个字符串,资源一旦变化这个值就会发生变更,与最后修改时间无关。所以可以很好地补充Last-Modified的问题
2)浏览器再次请求资源时,在请求头里带上If-None-Match,值为上次请求返回的ETag
3)服务端接收浏览器传过来的If-None-Match,并且计算请求资源的ETag值,然后进行对比,当ETag对比一样时,表示资源未修改,返回304 Not Modified,且不会返回资源内容,不过,仍然会返回新计算的ETag值。如果对比不一样,则返回新的资源。
4)浏览器收到304响应后,从缓存中加载资源

很多web服务器都默认开启Last-Modified/If-Modified-SinceETag/If-None-Match,并且是同时启用的。不过分布式系统里面,则有一些不同,这是因为:分布式系统里多台机器的Last-Modified必须保持一致,避免负载均衡到不同机器导致对比失败;分布式系统尽量关闭ETag,因为每台机器生成的ETag都会不一样

注意:浏览器并不会处理ETagLast-Modified的优先级,它只会保存下它们,并且在下次请求时相应地带上If-None-Match或者If-Modified-Since优先级的处理则交由服务端处理。一般来说,ETag方案被认为是强验证器,而Last-Modified则被认为是弱的。


参考资源