缓存(Cache)是计算机领域里的一个重要概念,是优化系统性能的利器。

由于链路漫长,网络时延不可控,浏览器使用 HTTP 获取资源的成本较高。所以,非常有必要把“来之不易”的数据缓存起来,下次再请求的时候尽可能地复用。这样,就可以避免多次请求 - 应答的通信成本,节约网络带宽,也可以加快响应速度。

试想一下,如果有几十 K 甚至几十 M 的数据,不是从网络而是从本地磁盘获取,那将是多么大的一笔节省,免去多少等待的时间。

实际上,HTTP 传输的每一个环节基本上都会有缓存,非常复杂。

缓存策略

在阐述HTTP不同缓存策略之前,我们需要知道用户刷新/访问行为 的手段分成三类:

  • 在URI输入栏中输入然后回车/通过书签访问

  • F5/点击工具栏中的刷新按钮/右键菜单重新加载

  • Ctl+F5 (完全不使用HTTP缓存)

不同的刷新手段,会导致浏览器使用不同的缓存策略,我们下面会分析到HTTP 缓存主要是通过请求和响应报文头中的对应 Header 信息,来控制缓存的策略。

HTTP缓存的类型很多,根据是否需要重新向服务器发起请求来分类包括两种:强制缓存 和 对比缓存。

http缓存请求相应头

先大概了解这些缓存字段是必须的,后面也会细说,先留个印象。

1. Cache-Control

请求/响应头,缓存控制字段,可以说是控制http缓存的最高指令,要不要缓存也是它说了算。

它有以下常用值

  • no-store:所有内容都不缓存(这个才是响应不被缓存的意思)。

  • no-cache:缓存,但是浏览器使用缓存前,都会请求服务器判断缓存资源是否是最新,它是个比较高贵的存在,因为它只用不过期的缓存。

  • max-age=x(单位秒) :请求缓存后的X秒不再发起请求,属于http1.1属性,与下方Expires(http1.0属性)类似,但优先级要比Expires高。

  • s-maxage=x(单位秒):代理服务器请求源站缓存后的X秒不再发起请求,只对CDN缓存有效(这个在后面会细说)

  • public: 客户端和代理服务器(CDN)都可缓存

  • private: 只有客户端可以缓存

2. Expires

响应头,代表资源过期时间,由服务器返回提供,GMT格式日期,是http1.0的属性,在与max-age(http1.1)共存的情况下,优先级要低。

3. Last-Modified

响应头,资源最新修改时间,由服务器告诉浏览器。

4. if-Modified-Since

请求头,资源最新修改时间,由浏览器告诉服务器(其实就是上次服务器给的Last-Modified,请求又还给服务器对比),和Last-Modified是一对,它两会进行对比

5. Etag

响应头,资源标识,由服务器告诉浏览器。主要是用来解决修改时间无法准确区分文件变化的问题。

6. if-None-Match

请求头,缓存资源标识,由浏览器告诉服务器(其实就是上次服务器给的Etag),和Etag是一对,它两会进行对比

7. s-maxage

(单位为s):同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。

8. max-stale

能容忍的最大过期时间。max-stale 指令标示了客户端愿意接收一个已经过期了的响应。如果指定了max-stale的值,则最大容忍时间为对应的秒数。如果没有指定,那么说明浏览器愿意接收任何age的响应(age表示响应由源站生成或确认的时间与当前时间的差值)。

9. min-fresh

min-fresh 的意思是缓存必须有效,而且必须在 x 秒后依然有效。比如“min-fresh=1”,这是绝对不允许过期的。这时有个资源在缓存里面已经存了7s了,其中 “max-age=10”,那么“7+1<10”,在 1s 之后还是新鲜的,因此是有效的。

强制缓存

在浏览器已经缓存数据的情况下,使用强制缓存去请求数据的流程是这样的:

从流程图可以看到,强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据,不需要再请求服务器,那么浏览器是如何判断缓存数据是否失效呢?
对于强制缓存来说,响应 header 中会有两个字段来标明失效规则(Expires/Cache-Control):

  • Expires:

Expires 是HTTP1.0的产物了,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。但是很多网站还是对它做了兼容。它的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。

但有一个问题是到期时间是由服务端生成的,如果客户端时间跟服务器时间不一致,这就会导致缓存命中的误差。

在HTTP 1.1 的版本,Expires被Cache-Control替代。

  • Cache-Control:

Cache-Control 是最重要的规则。常见的取值前面已经描述过,此处不再详细描述。

举个板栗

图中 Cache-Control 仅指定了max-age,所以默认为 private,缓存时间为 31536000秒(365天)
也就是说,在 365 天内再次请求这条数据,都会直接获取缓存数据库中的数据,直接使用。

对比缓存(协商缓存)

对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。

浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。

再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

第一次访问:

再次访问:

通过两图的对比,我们可以很清楚的发现,在对比缓存生效时,状态码为304,并且报文大小和请求时间大大减少。

原因是,服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。

对于对比缓存来说,缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递,接下来,我们分开介绍。

  • Last-Modified / If-Modified-Since

服务器响应请求时,会通过 Last-Modified HTTP 头告诉浏览器资源的最后修改时间,浏览器本地对资源缓存起来,之后再请求的时候,会带上一个HTTP头If-Modified-Since,这个值就是服务器上一次给的Last-Modified的时间,服务器会拿着浏览器传过来的时间比对资源当前最后的修改时间,如果大于If-Modified-Since,则说明资源修改过了,浏览器不能再使用缓存,服务器重新一份完整的资源浏览器,否则浏览器可以继续使用缓存,并返回304状态码

  • Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since)

服务器响应请求时,通过 Etag HTTP 头部告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定),浏览器再次请求时,就会带上一个头If-None-Match,这个值就是服务器上一次给的 Etag 的值,服务器比对一下资源当前的Etag是否跟If-None-Match一致,不一致则说明资源修改过了,浏览器不能再使用缓存,否则浏览器可以继续使用缓存,并返回304状态码

ETag 还有“强”“弱”之分。强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。

“ETag: W/"v2.6"
If-None-Match: W/"v2.6"

不管相关的实体值以何种方式发生了变化,强实体标签都要发生变化。而相关实体在语义上发生了比较重要的变化时,弱实体标签也应该发生变化。”

值得注意的是:Etag 的校验优先级高于 Last-Modified

什么时候应该使用 Etag 和 Last-Modified

如果服务器回送了一个实体标签,HTTP/1.1 客户端就必须使用实体标签验证器。如果服务器只回送了一个 Last-Modified 值,客户端就可以使用 If-Modified-Since 验证。如果实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样 HTTP/1.0 和 HTTP/1.1 缓存就都可以正确响应了。

除非 HTTP/1.1 原始服务器无法生成实体标签验证器,否则就应该发送一个出去,如果使用弱实体标签有优势的话,发送的可能就是个弱实体标签,而不是强实体标签。而且,最好同时发送一个最近修改值。

如果 HTTP/1.1 缓存或服务器收到的请求既带有 If-Modified-Since,又带有实体标签条件首部,那么只有这两个条件都满足时,才能返回 304 Not Modified 响应。

浏览器行为

对浏览器的不同行为对缓存的影响做一个总结。

  1. 浏览器地址栏回车,或者点击跳转按钮,前进,后退,新开窗口,这些行为,会让Expires,max-age生效,也就是说,这几种操作下,浏览器会判断过期时间,再考虑要不要发起请求,当然Last-Modified和Etag也有效。

  2. F5刷新浏览器,或者使用浏览器导航栏的刷新按钮,这几种,会忽略掉Expires,max-age的限制,浏览器会在请求头里加一个“Cache-Control: max-age=0” 强行发起请求,它可以配合 ETag 和 Last-Modified 使用,如果本地缓存还在,且服务器返回 304 ,依然可以使用本地缓存。

  3. CTRL+F5是强制请求,它其实是发了一个“Cache-Control: no-cache”,含义和“max-age=0”基本一样,就看后台的服务器怎么理解,通常两者的效果是相同的。

但事实上,我们很少用到地址栏回车,地址栏跳转,所以要触发缓存时间的判断,还需要特定的操作,站在我的理解,综合考虑下,才有了这么多网站的cache-control设置为no-cache,也就是使用缓存前都判断文件是否为最新,更为合理。

缓存和广告

读到这里,你一定已经意识到缓存可以提高性能并减少流量。知道缓存可以帮助用户,并为用户提供更好的使用体验,而且缓存也可以帮助网络运营商减少流量。

发布广告者的两难处境

你可能认为内容提供商会喜欢缓存。毕竟,如果到处都是缓存的话,内容提供商就不需要购买大型的多处理器 Web 服务器来满足用户需求了——他们不需要付过高的网络服务费,一遍一遍地向用户发送同样的数据。更好的一点是,缓存可以将那些漂亮的文章和广告以更快,甚至更好看的方式显示在用户的显示器上,鼓励他们去浏览更多的内容,看更多的广告。这就是内容提供商所希望的!吸引更多的眼球和更多的广告!

但这就是困难所在。很多内容提供商的收益都是通过广告实现的——具体来说,每向用户显示一次广告内容,内容提供商就会得到相应的收益。(可能还不到一两便士,但如果一天显示数百万条广告的话,这些钱就会叠加起来!)这就是缓存的问题——它们会向原始服务器隐藏实际的访问次数。如果缓存工作得很好,原始服务器可能根本收不到任何 HTTP 访问,因为这些访问都被因特网缓存吸收了。但如果你的收益是基于访问次数的话,你就高兴不起来了。

发布者的响应

现在,广告商会使用各种类型的“缓存清除”技术来确保缓存不会窃取他们的命中流量。他们会在内容上加上 no-cache 首部。他们会通过 CGI 网关提供广告。还会在每次访问时重写广告 URL。
这些缓存清除技术并不仅用于代理缓存。实际上,现在主要将其用于每个 Web 浏览器中都启用了的缓存。但是,如果某些内容提供商维护其命中率的行为太过火了,就会降低缓存为其站点带来的积极作用。

理想情况下,内容提供商会让缓存吸收其流量,而缓存会告诉内容提供商它们拦截了多少次命中。现在,缓存有好几种方式可以做到这一点。

一种解决方案就是配置缓存,每次访问时都与原始服务器进行再验证。这样,每次访问时都会将命中推向原始服务器,但通常不会传送任何主体数据。当然,这样会降低事务处理的速度。

日志迁移

理想的解决方案是不需要将命中传递给服务器的。毕竟,缓存就可以记录下所有的命中。缓存只要将命中日志发送给服务器就行了。实际上,为了保持内容提供商们 的满意度,有些大型缓存的提供商已经在对缓存日志进行人工处理,并将其传送给受影响的内容提供商了。

但是,命中日志很大,很难移动。而缓存日志并没有被标准化或被组织成独立的日志,以传送给单独的内容提供商。而且,这里面还存在着认证和隐私问题。

已经有一些高效(和不那么高效的)日志分发策略的建议了。但还没有哪个建议成熟到足以为 Web 软件厂商采用。很多建议都非常复杂,需要联合商业伙伴才能实现。有几家联合厂商已经开始开发广告收入改造工程的支撑框架了。

命中计数和使用限制

RFC 2227,“HTTP 的简单命中计数和使用限制”中定义了一种简单得多的方案。这个协议向 HTTP 中添加了一个称为 Meter 的首部,这个首部会周期性地将对特定 URL 的命中次数回送给服务器。通过这种方式,服务器可以从缓存周期性地获取对已缓存文档命中次数的更新。

而且,服务器还能控制在缓存必须向服务器汇报之前,其中的文档还可以使用多少次,或者为缓存文档设置一个时钟超时值。这种控制方式被称为使用限制;通过这种方式,服务器可以对缓存向原始服务器汇报之前,已缓存资源的使用次数进行控制。

总结

对于强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行对比缓存策略。

对于对比缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。

浏览器第一次请求:

浏览器再次请求时:

参考文章

http缓存详解,http缓存推荐方案

5分钟看懂系列:HTTP缓存机制详解

透视HTTP协议

彻底弄懂 HTTP 缓存机制及原理!

深入解析 HTTP 缓存控制的更多相关文章

  1. Android okHttp网络请求之缓存控制Cache-Control

    前言: 前面的学习基本上已经可以完成开发需求了,但是在项目中有时会遇到对请求做个缓存,当没网络的时候优先加载本地缓存,基于这个需求我们来学习一直okHttp的Cache-Control. okHttp ...

  2. CDN网络(一)之典型的CND架构与HTTP协议的缓存控制

    前言 本人以前在CDN厂商蓝汛就职过一年时间,利用脑子里还残留的一些CDN知识,结合现有的书籍材料,写点东西. what's the CDN CDN(content delivery Network) ...

  3. HTTP 缓存控制总结

    引言 通过网络获取内容既缓慢,成本又高:大的响应需要在客户端和服务器之间进行多次往返通信,这拖延了浏览器可以使用和处理内容的时间,同时也增加了访问者的数据成本.因此,缓存和重用以前获取的资源的能力成为 ...

  4. WEB请求过程(http解析,浏览器缓存机制,域名解析,cdn分发)

    概述 发起一个http请求的过程就是建立一个socket通信的过程. 我们可以模仿浏览器发起http请求,譬如用httpclient工具包,curl命令等方式. curl "http://w ...

  5. 什么是Web缓存控制(基于HTTP头域)

    这是一篇转载的知识性的文档,主要目的是为了让Web缓存相关概念更容易被开发者理解并应用于实际的应用环境中.为了简要起见,某些实现方面的细节被简化或省略了.如果你更关心细节实现则完全不必耐心看完本文,后 ...

  6. HTTP的缓存控制

    1.缓存的分类: (1)缓存分为服务端侧(server side,比如 Nginx.Apache)和客户端侧(client side,比如 web browser). (2)服务端缓存又分为 代理服务 ...

  7. HTTP缓存控制 总结

    一.HTTP响应头.请求头中与缓存控制的相关字段 二.一个页面访问缓存的流程 三.三种刷新的实际操作 四.如何设置缓存 一.HTTP响应头.请求头中与缓存控制的相关字段 浏览器向服务器发起请求后,服务 ...

  8. PHP flush sleep 输出缓存控制详解

    1 2 3 4 5 6 ob_start,flush,ob_flush for($i=0;$i<</SPAN>10;$i++) { echo $i.''; flush(); slee ...

  9. 重新想象 Windows 8.1 Store Apps (90) - 通信的新特性: 通过 HttpBaseProtocolFilter 实现 http 请求的缓存控制,以及 cookie 读写; 自定义 HttpFilter; 其他

    [源码下载] 重新想象 Windows 8.1 Store Apps (90) - 通信的新特性: 通过 HttpBaseProtocolFilter 实现 http 请求的缓存控制,以及 cooki ...

随机推荐

  1. linux 文件目录解释笔记(未完待续...)

    目录 应放置档案内容 /bin 系统有很多放置执行档的目录,但/bin比较特殊.因为/bin放置的是在单人维护模式下还能够被操作的指令. 在/bin底下的指令可以被root与一般帐号所使用,主要有:c ...

  2. docker(5)docker运行web应用

    前言 前面我们运行的容器并没有一些什么特别的用处. 接下来让我们尝试使用 docker 构建一个 web 应用程序. 我们将在docker容器中运行一个 Python Flask 应用来运行一个web ...

  3. OsgEarth开发笔记(三):Osg3.6.3+OsgEarth3.1+vs2019x64开发环境搭建(下)

    前言   上一篇编译了proj6.2.0.gdal3.2.1,本篇继续.   OsgEarth编译过程简介   OsgEarth的编译,是基于Osg和OsgEarth结合在一起的,先要编译Osg,然后 ...

  4. Codeforces错题本

    为什么我这么菜啊QAQ Codeforces 1364C

  5. Educational Codeforces Round 94 (Rated for Div. 2) String Similarity、RPG Protagonist、Binary String Reconstruction、Zigzags 思维

    题目链接:String Similarity 题意: 首先题目定义了两个串的相似(串的构成是0.1),如果两个串存在对于一个下标k,它们的值一样,那么这两个串就相似 然后题目给你一个长度为2n-1的串 ...

  6. hdu2430Beans(单调队列)

     Mr. Pote's shop sells beans now. He has N bags of beans in his warehouse, and he has numbered them ...

  7. Codeforces Round #547 (Div. 3) C. Polycarp Restores Permutation (数学)

    题意:有一长度为\(n\)的序列\(p\),现在给你\(q_i=p_{i+1}-q_i \ (1\le i\le n)\),问你是否能还原出原序列,如果能救输出原序列,否则输出\(-1\). 题解:由 ...

  8. 煎蛋网爬虫之JS逆向解析img路径

    图片使用js onload事件加载 <p><img src="//img.jandan.net/img/blank.gif" onload="janda ...

  9. Java15变量竟然没什么区别,八大基本数据类型你知道吗?

    变量是什么? 变量是用来为不同数据类型在内存中分配的空间用来储存该数据. 不同于python这样的弱类型语言,变量声明不需要定义数据类型,就和写数学方程式一般,谁等于谁即可.而Java这个发展了多个版 ...

  10. Java之大数相加

    之前参加某公司笔试,机试题目是大数相加,两大数是字符串形式,求和. 当时讨巧用的是BigDecimal类,但是发迷糊了,以为b1.add(b2)后,和就加到b1上了,结果一直输出不对. 其实应该是这样 ...