专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员的那些事  ·  清华大学:DeepSeek + ... ·  2 天前  
程序员小灰  ·  清华大学《DeepSeek学习手册》(全5册) ·  昨天  
OSC开源社区  ·  升级到Svelte ... ·  4 天前  
程序员的那些事  ·  成人玩偶 + ... ·  4 天前  
程序员小灰  ·  DeepSeek做AI代写,彻底爆了! ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

一文搞懂浏览器缓存机制

SegmentFault思否  · 公众号  · 程序员  · 2019-09-03 11:47

正文

最近在项目中遇到了 IE 浏览器因缓存问题未能成功向后端发送 GET 类型请求的 bug,然后顺藤摸瓜顺便看了看缓存的知识,觉得有必要总结跟大家分享一下。


在前端开发中,性能一直都是被大家所重视的一点,然而判断一个网站的性能最直观的就是看网页打开的速度。其中提高网页反应速度的一个方式就是使用缓存。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。


介绍


web 缓存是指一个 web 资源 (如 html 页面,图片,js,数据等) 存在于 web 服务器和客户端 (浏览器) 之间的副本。


缓存会根据进来的请求保存输出内容的副本;当下一个请求来到的时候,如果是相同的 URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个 URL 地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。至于浏览器和网站服务器是如何标识网站页面是否更新的机制,将在后面介绍。


1.1 web缓存的作用


web 缓存的作用显而易见:


  • 减少网络带宽消耗:无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当 Web 缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
  • 降低服务器压力:给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫机器人也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。
  • 减少网络延迟,加快页面打开速度:带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那 Web 缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。

1.2 web缓存的类型


web缓存大致可以分为以下几种类型 详细内容:


  • 数据库数据缓存
  • 服务器端缓存
  • 浏览器端缓存
  • web应用层缓存


浏览器通过代理服务器向源服务器发起请求的原理如下图:



浏览器先向代理服务器发起web请求,再将请求转发到源服务器。它属于共享缓存,所以很多地方都可以使用其缓存资源,因此对于节省流量有很大作用。


浏览器缓存是将文件保存在客户端,在同一个会话过程中会检查缓存的副本是否足够新,在后退网页时,访问过的资源可以从浏览器缓存中拿出使用。通过减少服务器处理请求的数量,用户将获得更快的体验


下面着重关注一下浏览器缓存。


web缓存的工作原理


所有的缓存都是基于一套规则来帮助他们决定什么时候使用缓存中的副本提供服务 (假设有副本可用的情况下,未被销毁回收或者未被删除修改) 。这些规则有的在协议中有定义 (如 HTTP 协议 1.0 和 1.1) ,有的则是由缓存的管理员设置 (如 DBA、浏览器的用户、代理服务器管理员或者应用开发者)。


2.1 浏览器端的缓存规则


对于浏览器端的缓存来讲,这些规则是在 HTTP 协议头和 HTML 页面的 Meta 标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是直接使用缓存中的副本,还是需要去源服务器获取更新的版本。


  1. 新鲜度 (过期机 制) :也就是缓存副本有效期。一个缓存副本必须满足以下任一条件,浏览器会认为它是有效的,足够新的,而直接从缓存中获取副本并渲染:
  • 含有完整的过期时间控制头信息 (HTTP 协议报头) ,并且仍在有效期内
  • 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度
  • 校验值 (验证机制) :服务器返回资源的时候有时在控制头信息带上这个资源的实体标签 Etag (Entity Tag) ,它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。

  • 2.2 浏览器缓存的控制


    2.2.1 使用 HTML的 Meta 标签


    <META HTTP-EQUIV="Pragma" CONTENT="no-cache">


    上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析 HTML 内容本身。可以通过这个页面测试你的浏览器是否支持:[Pragma No-Cache Test] (http://www.procata.com/cachetest/tests/pragma/index.php)。


    2.2.2 使用缓存有关的 HTTP 消息报头


    一个 URI 的完整 HTTP 协议交互过程是由 HTTP 请求和 HTTP 响应组成的。有关 HTTP 详细内容可参考《Hypertext Transfer Protocol — HTTP/1.1》、《HTTP协议详解》等。


    在 HTTP 请求和响应的消息报头中,常见的与缓存有关的消息报头有:



    稍微解释一下:


    1. Cache-Control


    • max-age (单位为 s) 指定设置缓存最大的有效时间,定义的是时间长短。当浏览器向服务器发送请求后,在 max-age 这段时间里浏览器就不会再向服务器发送请求了。我们来找个资源看下。比如 QQ 推广上的 css 资源,max-age=3600,也就是说缓存有效期为 3600 秒 (也就是 1h) 。于是在 1 天内都会使用这个版本的资源,即使服务器上的资源发生了变化,浏览器也不会得到通知。max-age 会覆盖掉 Expires,后面会有讨论


    • s-maxage (单位为s) 同 max-age,只用于共享缓存 (比如 CDN 缓存) 。比如,当 s-maxage=60时,在这 60 秒中,即使更新了 CDN 的内容,浏览器也不会进行请求。也就是说 max-age 用于普通缓存,而 s-maxage 用于代理缓存。如果存在 s-maxage,则会覆盖掉 max-age 和 Expires header。
    • public 指定响应会被缓存,并且在多用户间共享。也就是下图的意思。如果没有指定public 还是 private,则默认为 public。


    • private 响应只作为私有的缓存 (见下图) ,不能在用户间共享。如果要求 HTTP 认证,响应会自动设置为 private
    • no-cache 指定不缓存响应,表明资源不进行缓存,但是设置了 no-cache 之后并不代表浏览器不缓存,而是在获取缓存前要向服务器确认资源是否被更改。因此有的时候只设置 no-cache 防止缓存还是不够保险,还可以加上 private 指令,将过期时间设为过去的时间。
    • no-store 绝对禁止缓存,一看就知道如果用了这个命令当然就是不会进行缓存啦~每次请求资源都要从服务器重新获取。
    • must-revalidate 指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。

    cache-control 的种类这么多,然而怎么使用它们呢,参看下图:



    2. Expires


    缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说, Expires=max-age + 请求时间 ,需要和 Last-modified 结合使用。但在上面我们提到过,cache-control 的优先级更高。Expires 是 Web 服务器响应消息头字段,在响应 http 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。



    3. Last-modified & If-modified-since


    服务器端文件的最后修改时间,需要和 cache-control 共同使用,是检查服务器端资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送 If-Modified-Since 报头,询问 Last-Modified 时间点之后资源是否被修改过。如果没有修改,则返回码为 304,使用缓存;如果修改过,则再次去服务器请求资源,返回码和首次请求相同为 200,资源为服务器最新资源。


    4. Etag & & If-None-Match


    根据实体内容生成一段 hash 字符串,标识资源的状态,由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改,如果没有修改,过程如下:



    2.2.3 缓存报头种类与优先级


    1. Cache-Control 与 Expires


    Cache-Control 与 Expires 的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过 Cache-Control 的选择更多,设置更细致,如果同时设置的话,其优先级高于 Expires。


    2. Last-Modified 与 ETag


    你可能会觉得使用 Last-Modified 已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要 Etag (实 体标识) 呢?HTTP1.1 中 Etag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:


    • Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度
    • 如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存
    • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形


    Etag 是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。Etag的服务器生成规则和强弱 Etag 的相关内容可以参考,《互动百科-Etag》和《HTTP Header definition》,这里不再深入。


    3. Last-Modified/ETag 与 Cache-Control/Expires


    配置 Last-Modified/ETag 的情况下,浏览器再次访问统一 URI 的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个 304 回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;


    Cache-Control/Expires 则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时, Cache-Control/Expires 的优先级要高,即当本地副本根据 Cache-Control/Expires 发现还在有效期内时,则不会再次发送请求去服务器询问修改时间 Last-Modified 或实体标识 Etag 了。


    一般情况下,两者会配合一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时 Last-Modified/ETag 将能够很好利用 304,从而减少响应开销。


    2.2.4 哪些请求不能被缓存?


    无法被浏览器缓存的请求:


    • HTTP 信息头中包含 Cache-Control:no-cache,pragma:no-cache,或 Cache-Control:max-age=0 等告诉浏览器不用缓存的请求
    • 需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
    • 经过 HTTPS 安全加密的请求 (有人也经过测试发现,ie 其实在头部加入 Cache-Control: max-age 信息,firefox 在头部加入 Cache-Control:Public 之后,能够对 HTTPS 的资源进行缓存,参考《HTTPS的七个误解》)
    • POST 请求无法被缓存
    • HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存


    使用缓存流程






    请到「今天看啥」查看全文