浏览器HTTP协议缓存,如何理解并正确应用?

前端必须掌握的缓存知识,对于一个Web网页的打开速度直接关系到用户体验;而提高打开页面速度有很多方面需要优化,其中缓存可以说是性能优化中简单高效的一种优化方式了。

JavaScript
发布于 2018-06-07 阅读 1.8k

前端必须掌握的缓存知识,对于一个Web网页的打开速度直接关系到用户体验;而提高打开页面速度有很多方面需要优化,其中缓存可以说是性能优化中简单高效的一种优化方式了。

为什么要使用缓存?

  1. 缩短网页请求资源的距离,减少延迟;
  2. 减少带宽,降低网络负荷;
  3. 降低服务器的压力。

所以使用浏览器缓存对于用户和运营者来说都是很有必要的,降低成本的同时还是可以明显加快页面打开速度,达到更好的体验。

浏览器端的缓存位置

从HTTP请求获取资源的优先级来说,浏览器端的缓存位置依次为:Service Worker、Memory Cache、Disk Cache及Push Cache。

至于这四种缓存位置的概念不做过多叙述,只简单说明...

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Memory Cache

Memory Cache 是存储在内存中的缓存,主要缓存网页中抓取到的静态资源,存储于内存中。读取速度快,但是缓存持续性差,会随着进程的释放而释放。

Disk Cache

Disk Cache 是存储在硬盘中的缓存,读取速度慢,但是比 Memory Cache存储容量大和时间长。

Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

HTTP协议缓存策略

Expires策略

Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

它指定的是具体的过期日期而不是秒数。例如:Expires: Wed, 22 Oct 2018 08:41:00 GMT

Expires 是 HTTP/1.0 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。因为很多服务器跟客户端存在时钟不一致的情况,所以最好还是使用 Cache-Control

Cache-Control策略

Cache-Control相比于Expires选择更多,设置更细致,属于 HTTP/1.1 的产物。为了兼容HTTP/1.0与HTTP/1.1,两种策略可以同时设置使用,但是其优先级高于Expires。

它也是在服务器响应消息头中设置,并且可以组合使用多种指令(N代表秒数):

指令 作用
public 所有内容都将被缓存(客户端和代理服务器都可缓存)
private 所有内容只有客户端可以缓存
no-store 不缓存任何响应
no-cache 客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定
max-age=N 表示缓存内容将在N秒后失效
s-maxage=N 作用与max-age一样,优先级高于max-age
max-stale=N N秒内,即使缓存过期,也使用改缓存
min-fresh=N 在N秒内获取最新的响应
isCache=>condition: 是否需要缓存?
noStore=>operation: no store
isSame=>condition: 资源要求是否强一致?
noCache=>operation: no cache
isAllow=>condition: 允许代理缓存?
public=>operation: public
private=>operation: private
cacheTime=>operation: 允许资源缓存时间
maxAge=>operation: max-age=N

isCache(yes)->isSame
isCache(no)->noStore
isSame(yes,right)->noCache->isAllow
isSame(no)->isAllow
isAllow(yes)->public->cacheTime
isAllow(no)->private->cacheTime
cacheTime(right)->maxAge

Last-Modified和If-Modified-Since

浏览器在第一次访问资源时,在响应头中添加 Last-Modified,值是这个资源在服务器上的最后修改时间,例如;Last-Modified: Fri, 22 Jul 2019 01:47:00 GMT

浏览器下一次请求这个资源,浏览器检测到有 Last-Modified的存在,于是在请求头中添加 If-Modified-Since,值就是 Last-Modified中的值;

服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,那么直接以304形式通知浏览器使用本地缓存即可,如果 If-Modified-Since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是以200形式将新的资源返回给浏览器。

Last-Modified 存在一些弊端:

  1. 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  2. 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETagIf-None-Match

ETag和If-None-Match

participant 浏览器 as b
participant 服务器 as s
b->s: 请求资源
s-->>b: 返回结果+响应头携带ETag标识
b->s: 将ETag标识,放到请求头If-None-Match,\n再次向服务器请求资源
s-->>s: 比较ETag是否一致
note right of s: 只要资源有变化,\nEtag就会重新生成
s-->>b: 一致:以200返回资源+新的ETag \n不一致:以304返回通知从缓存取

Etag是每一次请求资源时,携带在响应头中返回的一个参数,是该资源的唯一标识,只要资源有变化,Etag就会重新生成;

再次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头里的If-None-Match里,服务器将会把If-None-Match跟自己服务器上该资源的ETag比较是否一致,判断资源相对客户端而言是否被修改过。

如果服务器发现ETag匹配不上,那么直接以200形式将新的资源和新的ETag返回给浏览器;如果ETag是一致的,则直接以304形式通知浏览器使用本地缓存即可。

应用流程及总结

s=>start: 开始
e=>end: 结束
browser=>operation: 浏览器
isCache=>condition: 是否存在缓存?
request=>operation: 向服务器请求
response=>operation: 服务器返回结果
isExpired=>condition: 是否过期?
readCache=>operation: 读取缓存
paraRequest=>operation: 携带缓存标识向服务器发出请求
isUpdate=>condition: 是否有更新
againReturn=>operation: 返回最新资源和缓存标识
page=>inputoutput: 页面渲染

browser->isCache
isCache(yes,right)->isExpired
isCache(no)->request
request->response->page
isExpired(yes)->paraRequest
isExpired(no)->readCache->page
paraRequest->isUpdate
isUpdate(yes,left)->againReturn(right)->page
isUpdate(no)->readCache
  • 浏览器端缓存分为200 from cache和304 not modified;
  • HTTP协议中Cache-Control 和 Expires可以用来设置新鲜度的限值,前者是HTTP1.1中新增的响应头,后者是HTTP1.0中的响应头;
  • max-age(单位为s)而Expires指定的是具体的过期日期而不是秒数;
  • Cache-Control和Expires同时使用的话,Cache-Control会覆盖Expires;
  • 客户端不用关心ETag值如何产生,只要服务在资源状态发生变更的情况下将ETag值发送给它就行;
  • Apache默认通过FileEtag中FileEtag INode Mtime Size的配置自动生成ETag(当然也可以通过用户自定义的方式);
  • ETag常与If-None-Match或者If-Match一起,由客户端通过HTTP头信息(包括ETag值)发送给服务端处理;
  • Last-Modified常与If-Modified-Since一起由客户端将Last-Modified值包括在HTTP头信息中发给服务端进行处理;
  • 有些文档资源周期性的被重写,但实际内容没有改变。此时文件元数据中会显示文件最近的修改日期与If-Modified-Since不相同,导致不必要的响应。

参考资料

  1. 深入理解浏览器的缓存机制
  2. 前端必须要懂的浏览器缓存机制
  • 本文类型: 原创
  • 本文出处:
  • 版权说明: 本站内容均采用©BY-NC-SA许可协议,版权归作者和本站所有!欢迎转载,但未经作者同意必须在文章页面注明原文出处,否则保留追究法律责任的权利。

坤尘记

I AM KUNCHEN!思绪如风,总会在某处停留;用轻灵的文字书写关于技术生活的奇思妙想。

浙ICP备2020045526号