• 浏览器缓存机制


    1. 首先得明确 http 缓存的好处

    • 减少了冗余的数据传输,减少网费
    • 减少服务器端的压力
    • Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间
    • 加快客户端加载网页的速度

    2. 常见 http 缓存的类型

    • 私有缓存(一般为本地浏览器缓存)
    • 代理缓存

    3. 然后谈谈本地缓存

    本地缓存是指浏览器请求资源时命中了浏览器本地的缓存资源,浏览器并不会发送真正的请求给服务器了。它的执行过程是

    • 第一次浏览器发送请求给服务器时,此时浏览器还没有本地缓存副本,服务器返回资源给浏览器,响应码是200 OK,浏览器收到资源后,把资源和对应的响应头一起缓存下来
    • 第二次浏览器准备发送请求给服务器时候,浏览器会先检查上一次服务端返回的响应头信息中的Cache-Control,它的值是一个相对值,单位为秒,表示资源在客户端缓存的最大有效期,过期时间为第一次请求的时间减去Cache-Control的值,过期时间跟当前的请求时间比较,如果本地缓存资源没过期,那么命中缓存,不再请求服务器
    • 如果没有命中,浏览器就会把请求发送给服务器,进入缓存协商阶段。

    与本地缓存相关的头有:Cache-ControlExpiresCache-Control有多个可选值代表不同的意义,而Expires就是一个日期格式的绝对值。

    3.1 Cache-Control

    Cache-ControlHTPP缓存策略中最重要的头,它是HTTP/1.1中出现的,它由如下几个值

    • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载
    • no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源
    • public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
    • private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
    • max-age:从当前请求开始,允许获取的响应被重用的最长时间(秒)。
    • must-revalidate,当缓存过期时,需要去服务端校验缓存的有效性。
    # 例如:
    
    Cache-Control: public, max-age=1000 
    # 表示资源可以被所有用户以及代理服务器缓存,最长时间为1000秒。
    
    • 1
    • 2
    • 3
    • 4

    注意,虽然你可能在其他资料中看到可以使用 meta 标签来设置缓存,比如像下面的形式:

    • 1

    但在 HTML5 规范中,并不支持这种方式,所以尽量不要使用 meta 标签来设置缓存

    3.2 Expires

    ExpiresHTTP/1.0出现的头信息,同样是用于决定本地缓存策略的头,它是一个绝对时间,时间格式是如Mon, 10 Jun 2015 21:31:12 GMT,只要发送请求时间是在Expires之前,那么本地缓存始终有效,否则就会去服务器发送请求获取新的资源。如果同时出现Cache-Control:max-ageExpires,那么max-age优先级更高。他们可以这样组合使用

    Cache-Control: public
    Expires: Wed, Jan 10 2018 00:27:04 GMT
    
    • 1
    • 2

    3.3 所谓的缓存协商

    当第一次请求时服务器返回的响应头中存在以下情况时

    • 没有 Cache-ControlExpires
    • Cache-ControlExpires 过期了
    • Cache-Control 的属性设置为 no-cache

    那么浏览器第二次请求时就会与服务器进行协商,询问浏览器中的缓存资源是不是旧版本,需不需要更新,此时,服务器就会做出判断,如果缓存和服务端资源的最新版本是一致的,那么就无需再次下载该资源,服务端直接返回304 Not Modified 状态码,如果服务器发现浏览器中的缓存已经是旧版本了,那么服务器就会把最新资源的完整内容返回给浏览器,状态码就是200 Ok,那么服务端是根据什么来判断浏览器的缓存是不是最新的呢?其实是根据HTTP的另外两组头信息,分别是:Last-Modified/If-Modified-SinceETag/If-None-Match

    Last-Modified 与 If-Modified-Since

    具体工作流程如下:

    • 浏览器第一次请求资源时,服务器会把资源的最新修改时间Last-Modified:Thu, 29 Dec 2011 18:23:55 GMT放在响应头中返回给浏览器
    • 第二次请求时,浏览器就会把上一次服务器返回的修改时间放在请求头If-Modified-Since:Thu, 29 Dec 2011 18:23:55发送给服务器,服务器就会拿这个时间跟服务器上的资源的最新修改时间进行对比
    • 服务端再次收到请求,根据请求头 If-Modified-Since 的值,判断相关资源是否有变化,如果没有,则返回 304 Not Modified,并且不返回资源内容,浏览器使用资源缓存值;否则正常返回资源内容,且更新Last-Modified 响应头内容。

    如果两者相等或者大于服务器上的最新修改时间,那么表示浏览器的缓存是有效的,此时缓存会命中,服务器就不再返回内容给浏览器了,同时Last-Modified头也不会返回,因为资源没被修改,返回了也没什么意义。如果没命中缓存则最新修改的资源连同Last-Modified头一起返回

    这种方式虽然能判断缓存是否失效,但也存在两个问题:

    • 精度问题Last-Modified 的时间精度为秒,如果在 1 秒内发生修改,那么缓存判断可能会失效;
    • 准度问题,考虑这样一种情况,如果一个文件被修改,然后又被还原,内容并没有发生变化,在这种情况下,浏览器的缓存还可以继续使用,但因为修改时间发生变化,也会重新返回重复的内容。
    # 第一次请求返回的响应头
    Cache-Control:max-age=3600
    Expires: Fri, Jan 12 2018 00:27:04 GMT
    Last-Modified: Wed, Jan 10 2018 00:27:04 GMT
    
    • 1
    • 2
    • 3
    • 4
    # 第二次请求的请求头信息
    If-Modified-Since: Wed, Jan 10 2018 00:27:04 GMT
    
    • 1
    • 2

    这组头信息是基于资源的修改时间来判断资源有没有更新,另一种方式就是根据资源的内容来判断,就是接下来要讨论的 ETagIf-None-Match

    ETag与If-None-Match

    为了解决精度问题和准度问题,HTTP 提供了另一种不依赖于修改时间,而依赖于文件哈希值的精确判断缓存的方式,那就是响应头部字段 ETag 和请求头部字段 If-None-Match。

    ETag/If-None-MatchLast-Modified/If-Modified-Since的流程其实是类似的,唯一的区别是它基于资源的内容的摘要信息(比如MD5 hash)来判断

    浏览器发送第二次请求时,会把第一次的响应头信息ETag的值放在If-None-Match的请求头中发送到服务器,与最新的资源的摘要信息对比,如果相等,取浏览器缓存,否则内容有更新,最新的资源连同最新的摘要信息返回。用ETag的好处是如果因为某种原因到时资源的修改时间没改变,那么用ETag就能区分资源是不是有被更新。

    具体工作流程如下:

    • 浏览器第一次请求资源,服务端在返响应头中加入 Etag 字段,Etag 字段值为该资源的哈希值
    • 当浏览器再次跟服务端请求这个资源时,在请求头上加上 If-None-Match,值为之前响应头部字段 ETag 的值;
    • 服务端再次收到请求,将请求头 If-None-Match 字段的值和响应资源的哈希值进行比对,如果两个值相同,则说明资源没有变化,返回 304 Not Modified;否则就正常返回资源内容,无论是否发生变化,都会将计算出的哈希值放入响应头部的 ETag 字段中

    这种缓存比较的方式也会存在一些问题,具体表现在以下两个方面。

    • 计算成本。生成哈希值相对于读取文件修改时间而言是一个开销比较大的操作,尤其是对于大文件而言。如果要精确计算则需读取完整的文件内容,如果从性能方面考虑,只读取文件部分内容,又容易判断出错。
    • 计算误差。HTTP 并没有规定哈希值的计算方法,所以不同服务端可能会采用不同的哈希值计算方式。这样带来的问题是,同一个资源,在两台服务端产生的 Etag 可能是不相同的,所以对于使用服务器集群来处理请求的网站来说,使用 Etag 的缓存命中率会有所降低。

    需要注意的是,强制缓存的优先级高于协商缓存,在协商缓存中,Etag 优先级比 Last-Modified

    # 第一次请求返回的响应头:
    
    Cache-Control: public, max-age=31536000
    ETag: "15f0fff99ed5aae4edffdd6496d7131f"
    
    • 1
    • 2
    • 3
    • 4
    # 第二次请求的请求头信息:
    
    If-None-Match: "15f0fff99ed5aae4edffdd6496d7131f"
    
    • 1
    • 2
    • 3

    缓存位置

    浏览器缓存的位置的话,可以分为四种,优先级从高到低排列分别👇

    • Service Worker
    • Memory Cache
    • Disk Cache
    • Push Cache

    Service Worker

    这个应用场景比如PWA,它借鉴了Web Worker思路,由于它脱离了浏览器的窗体,因此无法直接访问DOM。它能完成的功能比如:离线缓存消息推送网络代理,其中离线缓存就是Service Worker Cache

    Memory Cache

    指的是内存缓存,从效率上讲它是最快的,从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。

    Disk Cache

    存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,优势在于存储容量和存储时长。

    Disk Cache VS Memory Cache

    两者对比,主要的策略👇

    • 内容使用率高的话,文件优先进入磁盘
    • 比较大的JS,CSS文件会直接放入磁盘,反之放入内存。

    Push Cache

    推送缓存,这算是浏览器中最后一道防线吧,它是HTTP/2的内容

    浏览器缓存总结

    浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下

    • 先根据这个资源的一些 http header 判断它是否命中强缓存,先检查Cache-Control,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;
    • 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些request header验证这个资源是否命中协商缓存,称为http再验证,如果命中,服务器将请求返回,但不返回资源,而是返回304告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;(服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新)
    • 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源; 区别是,强缓存不对发送请求到服务器,但协商缓存会。
    • 当协商缓存也没命中时,服务器就会将资源发送回客户端。
    • 当 ctrl+f5 强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;
    • 当 f5刷新网页时,跳过强缓存,但是会检查协商缓存;

    强缓存

    • Expires(该字段是 http1.0 时的规范,值为一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)
    • Cache-Control:max-age(该字段是 http1.1的规范,强缓存利用其 max-age 值来判断缓存资源的最大生命周期,它的值单位为秒)

    协商缓

    • Last-Modified(值为资源最后更新时间,随服务器response返回,即使文件改回去,日期也会变化)
    • If-Modified-Since(通过比较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存)
    • ETag(表示资源内容的唯一标识,随服务器response返回,仅根据文件内容是否变化判断)
    • If-None-Match(服务器通过比较请求头部的If-None-Match与当前资源的ETag是否一致来判断资源是否在两次请求之间有过修改,如果没有修改,则命中协商缓存)
  • 相关阅读:
    SQL的语法
    aop+springboot实现数据字典表
    C 语言中关键字const
    公司电脑如何限制安装软件
    leetcode做题笔记169. 多数元素
    Prometheus-Operator使用ServiceMonitor监控配置时遇坑与解决总结
    Find My婴儿车|苹果Find My技术与婴儿车结合,智能防丢,全球定位
    Android Dialog相关设置
    【OpenVINO™】在C#中使用 OpenVINO™ 部署 YOLOv10 模型实现目标
    leetcode/链表排序
  • 原文地址:https://blog.csdn.net/php_martin/article/details/125869277