浏览器缓存机制


title: 浏览器缓存机制
time: 2019-11-19
author: wsm
mail: [email protected]
github: https://github.com/Geronimomiao/advance

分类


  • Service worker
  • Memory cache
  • Disk cache
  • Push cache

Service worker


取决于开发者如何设置它
是持久化的,即使tab页关闭或者浏览器重启,保存在Service Worker缓存里的资源不会消失
一种删除Service Worker缓存的方法是使用JS代码,cache.delete(resource);还有一种导致缓存被删除的情况是触发了系统的存储空间上限,此时页面的Service Worker缓存连同indexedDB, localStorage等都会一起被回收掉
Service Worker仅仅在某个限定的作用域内生效,大多数情况下仅对单个host内的文档发起的请求生效

Memory cache


内存缓存(Memory Cache)是一个巨大的资源容器,渲染引擎(renderer)在渲染当前文档期间抓取的所有的资源都储存在其中,并且在文档生命周期内一直存在
一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验

Disk cache(HTTP cache)


Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上
当浏览器需要空间去储存更加新鲜或者更加重要的缓存的时候,旧的缓存就会被删除

浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?

对于大文件来说,大概率是不存储在内存中的,反之优先
当前系统内存使用率高的话,文件优先存储进硬盘

Push cache


Push Cache是HTTP/2推送的资源存储的地方。它是HTTP/2会话的一部分。如果HTTP/2会话关闭了,储存在其中的资源都会消失。从不同的会话发起的请求将不会命中Push Cache中的资源。所有未被使用的资源在Push Cache会储存优先的时间(Chromium浏览器大约5分钟)

强缓存


不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control

  • HTTP/1.0

    • Pragma 请求中包含Pragma的效果跟在头信息中定义Cache-Control: no-cache
    • Expires=max-age + 请求时间,需要和Last-modified结合使用 Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求
  • HTTP/1.1

    • Cache-Control
      • no-store 缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容
      • no-cache 每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本
      • private 专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中
      • public 可以被任何中间人(译者注:比如中间代理、CDN等)缓存
      • max-age=<seconds> 资源能够被缓存(保持新鲜)的最大时间
      • must-revalidate 浏览的过程中也会触发缓存验证

协商缓存


协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除,这个过程叫做缓存驱逐。另一方面,当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于HTTP是C/S模式的协议,服务器更新一个资源时,不可能直接通知客户端更新缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源(缓存副本)就是新鲜的,当过了过期时间后,该资源(缓存副本)则变为陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个If-None-Match头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,可以节省一些带宽。若服务器通过 If-None-Match 或 If-Modified-Since判断后发现已过期,那么会带有该资源的实体内容返回

浏览器缓存机制

  • 协商缓存生效,返回304和Not Modified
  • 协商缓存失效,返回200和请求结果
    协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header
浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200

Last-Modified 存在一些弊端

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

未解决上述弊端 HTTP/1.1 出现了 ETag和If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成
浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可

用户行为对浏览器缓存的影响


  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容

补充


如果什么缓存策略都没设置,那么浏览器会怎么处理?
对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存

浏览器缓存机制