关于缓存的那些传说

xiaoxiao2021-02-28  12

原文链接:https://calendar.perfplanet.com/2016/a-tale-of-four-caches/

译者说:

英文好建议直接阅读原文,作者写的还是比较逗的,译者可能幽默天赋有限,另外还有一些意译所以对不起读者了


最近几天大家针对preload、、http/2 push和service worker等浏览器缓存讨论了很多,但是还是有很多困惑。

所以,我想跟大家讲个故事,来让大家更加了解一个请求的命运之旅和它如何找到匹配的缓存资源

下面的故事是基于Chromium的术语和概念,不过与其他的浏览器并没有本质上的不同。

Questy的旅程

Questy是一个在渲染引擎(也被简称为渲染器)创建的请求,它迫切地想在当前标签页关闭、文档卸载之前,找到一个能够使其成为人赢并且以后一起愉快玩耍的资源。

所以Questy开始了追求幸福之旅。但是它能找到他想要的资源吗?

最近要寻找的一个地方是……

内存缓存

内存缓存有一个很大的容器,里面装满了资源。它包含所有渲染器拿来作为文档组成部分的资源,并且会在文档的生命周期中一直保留。这意味着如果Questy正在寻找的资源是在文档中已经存在的,那么这个资源将会在内存缓存中被找到。

也许“短期内存缓存”可能会更加贴切一些,因为内存缓存会在导航结束前被保留,但是在某些情况,时间甚至会更短。

!The short term memory cache and its container

很多潜在的原因会造成Questy正在寻找的资源却已经被获取了。

最可能的原因是预加载,如果Questy是被HTML解析器创建的DOM节点创建的,那么Questy所需的资源可能会在HTML标记化阶段,就已经被预加载器提前被获取了。

显式预加载指令()也是提前加载的资源被存储在内存缓存里的很重要的原因。

另外,也可能是过去的DOM节点或者CSS规则触发了相同资源的重复获取。例如,一个页面包含多个同样src属性的元素,这情况就只会获取一次资源。内存缓存机制使这几个对应同样资源的元素获取内存缓存里面的同一个资源。

然而,内存缓存不会轻易将命中的资源交给请求。显然为了请求和资源相互匹配,他们必须拥有匹配的URLS。但是这还不够。他们必须有相同的资源类型(正因为如此一个script资源才不会匹配上img资源),相同CORS模式和一些其他特征。

规范中没有明确定义内存缓存中的请求匹配特征,因此不同浏览器的实现可能有轻微不同。

内存缓存不会纠结HTTP语义。即使存储的资源有max-age=0或者no-cache cache-control等头部字段,也并不会在内存缓存生效。因为资源在当前导航是可以重复使用的,HTTP的语义并不重要。

唯一的例外是no-store指令,在某些情况下内存缓存会遵循它(例如,当资源被单独节点重用时)。

所以,Questy径直走到内存缓存跟前问它是否有匹配的资源。不幸的是没有找到。

Questy没有放弃。它穿过资源时间轴和开发者工具网络定位点,并且它注册为寻找资源的请求,留下它请求资源的足迹(这意味着如果她能找到匹配的资源,则会出现在开发中工具和资源计时器中)。

完成了这些官方登记后,她继续向前……

Service Worker 缓存

和内存缓存不一样,Service Woker不走寻常路。他的行为难以预测。因为他只遵循开发者告诉他的规则。

首先,Service Worker只存在在安装注册的页面。另外它的执行逻辑是被web开发者用JavaScript开发的,而不是浏览器内置的,Questy不知道是否能寻找到资源,又或者即便找到了,这个资源会是Questy梦寐以求的那个吗?储存在缓存中的他会是真正匹配的资源吗?又或者是一个被逻辑扭曲的Service Worker开发者精心设计的响应?

没有人知道。因为Service Workers 是运行他们自己的逻辑的,包括匹配请求和潜在资源,包装响应请求,总之可以执行任何它们觉得合适的行为。

Service Worker拥有保存资源的缓存API。相对于内存缓存的主要的不同点是它是持续存在的。资源被持久储存在缓存里,即使标签页关闭或者浏览器重启。除非开发者明确的驱逐它们(使用cache.delete(resource)),它们将被从缓存中驱逐。另一种情况是如果浏览器存储空间不足,那么它会将整个Service Worker缓存,还有其他的源缓存如indexedDB、localStorage等都清除掉。正因为如此,Service Worker能确保它的缓存是和其他缓存同步的。

Service Worker只会响应一个host的请求。因此也只会控制在该host范围内的文档请求。

Questy走到Servcie Worker跟前询问是否有匹配它的资源。但是Servcie Worker从没见过这个范围内有这个资源,因此也没有相应的资源给予Questy。所以Service Worker送Questy继续上路了(使用fetch()),进而在变化莫测的网络堆栈里面继续搜寻资源。

那么在网络堆栈里面,寻找资源的最佳地点是……

HTTP缓存

HTTP缓存(在朋友间也被称为磁盘缓存)跟Questy之前遇见的缓存十分不同。

一方面,它是持久的,允许资源在不同会话甚至网站中重用。如果资源在一个网站上被缓存了,HTTP缓存可能会让它在其他的网站重用。

另一方面,HTTP缓存遵循HTTP语义(名字揭示一切)。它很幸福地为他认为有效的资源(基于由响应的缓存头标识缓存生命周期)服务,重新验证需要验证的资源,拒绝存储不应该存储的资源。

因为他是长久缓存,也需要删除资源,与Service Worker不同的是,当缓存需要更多空间来储存更重要或更新的资源时,HTTP缓存会一个一个的删除资源。

HTTP缓存有一个基于内存的组件,在这个组件中会处理匹配缓存资源的请求。但是如果它真找到了匹配的资源,它需要进行一个昂贵的操作,从磁盘取回这个资源。

之前我们提到HTTP缓存遵循HTTP语义。这基本上是正确的。但是还是有一个例外,当HTTP缓存存储有有效期资源时。浏览器有为下一个导航预取资源的能力。可以使用明确的代码()或者浏览器自定义的代码。那些预取的资源在下个导航到来之前需要一直保留,即使它们不可缓存。所以当这样一个预取资源到达HTTP缓存时,他会被缓存(在没有重新生效前提下)五分钟。

HTTP缓存看起来很严肃,不过Questy还是鼓起勇气走到它面前问是否有它的匹配资源。结果并没有。

继续变化莫测和可啪的网络旅程,但是Questy坚信不论有什么苦难都不能阻挡他寻找资源的步伐。所以他继续上路了。在它继续在网络里前行的时候,它发现了一个相应的HTTP2会话,突然它看见了…..

Push缓存

push缓存(更好的称呼是“无人认领的推送流容器”,但是名字不太容易记住)是HTTP2推送资源存储的地方。他们被存储为HTTP2会话的一部分,这有几个特殊含义。

这个容器是不持久的。如果这个会话终止了,所有没有声明的资源(也就是不匹配请求的资源)都消失了。如果资源在另一个不同的HTTP2会话中获取,它将不会被匹配。除此以外,资源将会保留在推送缓存容器中一段有限的时间。(在基于chromium的浏览器中约为五分钟)

push缓存根据URL匹配请求对应的资源,还有它的各种请求头,但是并不遵循严格的HTTP语义。

push缓存也没有明确的规范,具体行为根据浏览器、操作系统和其他HTTP2客户端不同而不同。

Questy没有什么信心,但是还是向前询问push缓存是否存在匹配的缓存。出乎他的意料,竟然找到了!Questy收到了对应资源(意味着它把这个HTTP2流从无人认领资源的容器中移除了),高兴得像个蛤蜊一样。现在它可以开始带着它的资源返程渲染器了。

在返程中,他们穿过HTTP缓存,停留了一下并且存储下来了一份资源的拷贝,以备以后将来有请求需要它。

当它们走出网络堆栈返回Service Worker处时,在返回渲染器之前,Service Worker也在其缓存中留下了一份资源拷贝。

最终,当它们回到渲染器,内存缓存会保留一个指向资源的引用(不是拷贝),在同一个导航会话中它将会分配这个资源给将来的请求。

自此它们过上了幸福的生活,直到文档被移除,所有的部分都被垃圾回收。

但那又是另一天的故事了

总结

所以,我们能从Questy的旅途中学到什么?

不同的请求能够匹配不同的浏览器缓存请求命中不同的缓存会对这个请求反映在开发者工具和资源时间轴有所影响推送的缓存不是持久的除非它的流被一个请求匹配到了不能缓存的预加载资源不会保留到下一个导航,这是预加载和预取的主要不同之一美中不足的是以上提及的部分有很多标准未明确规定的,导致不同浏览器会有不同的行为。我们需要修复它们。

总之,如果你使用预取,HTTP2 push,Service Worker或者其他先进科技,当你尝试去优化你的网站时,你可能会注意到一些内部缓存实现不一致的问题。意识到这些内部缓存问题,他们怎么执行可能帮助你更好的理解缓存将会如何运行,希望帮助你避免不必要的挫折。

原文完

转载请注明原文地址: https://www.6miu.com/read-200236.html

最新回复(0)