整个 Web 系统架构在HTTP 协议之上, 利用 HTTP 的缓存机制不仅可以极大地减少服务器负载, 更重要的是加速页面的载入,以及减少用户的流量消耗。 快速到达和易于访问是 Web 与生俱来的特性, 其缓存机制也早已被服务器和浏览器厂商广泛地实现, 我们作为 Web 内容的作者何乐而不为呢?

HTTP 缓存简介

谈起 HTTP 缓存你首先想到的一定是磁盘缓存,以及 304 状态码。 这是浏览器处理缓存的两种情况:

浏览器缓存有几个(关于浏览器缓存你还有哪些疑问)(1)

每个状态的详细说明如下:

1、Cache-Control

Cache-Control 在 HTTP 响应头中,用于指示代理和 UA 使用何种缓存策略。比如:

当Cache-Control为可缓存时,同时可指定缓存时间(比如public, max-age:86400)。 这意味着在 1 天(60x60x24=86400)时间内,浏览器都可以直接使用该缓存。 当然浏览器也有权随时丢弃任何一项缓存,因此这里可能有一致性问题。

2、Etag

如果资源本身确实会随时发生改动,还用 Cache-Control 就会使用户看到的页面得不到更新。 但如果还希望利用 HTTP 缓存,这就需要有条件的(conditional)HTTP 请求。

HTTP协议规格说明定义ETag为“被请求变量的实体标记”,弱实体只要内容语义没变即可,强实体指字节必须完全一致,建议使用弱实体。

如果响应体包含Etag字段,则浏览器在下次发送请求时会带 If-None-Match 头字段, 来询问服务器该版本是否仍然可用。如果服务器发现该版本仍然是最新的, 就可以返回 304 状态码指示 UA 继续使用缓存。

类似服务器端返回的格式:

ETag: W/"3ae83efccfc543bad6866e325cd8bfb9"

客户端的查询更新格式是这样的:

If-None-Match:W/"3ae83efccfc543bad6866e325cd8bfb9"

如果ETag没改变,则返回状态304。

3、Last-Modified

在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是请求的资源,同时有一个Last-Modified的属性标记(HttpReponse Header)此文件在服务期端最后被修改的时间,格式类似这样:

Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT

客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头(HttpRequest Header),询问该时间之后文件是否有被修改过:

If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT

如果服务器端的资源没有变化,则自动返回HTTP 304(NotChanged)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

注:如果If-Modified-Since的时间比服务器当前时间(当前的请求时间request_time)还晚,会认为是个非法请求

4、Expires

给出的日期/时间后,被响应认为是过时。如Expires:Thu, 02 Apr 2009 05:14:08 GMT

需和Last-Modified结合使用。用于控制请求文件的有效时间,当请求数据在有效期内时客户端浏览器从缓存请求数据而不是服务器端。当缓存中数据失效或过期,才决定从服务器更新数据。

5、Last-Modified和Expires

Last-Modified标识能够节省一点带宽,但是还是逃不掉发一个HTTP请求出去,而且要和Expires一起用。而Expires标识却使得浏览器干脆连HTTP请求都不用发,比如当用户F5或者点击Refresh按钮的时候就算对于有Expires的URI,一样也会发一个HTTP请求出去,所以,Last-Modified还是要用的,而且要和Expires一起用。

6、Etag和Expires

如果服务器端同时设置了Etag和Expires时,Etag原理同样,即与Last-Modified/Etag对应的HttpRequestHeader:If-Modified-Since和If-None-Match。我们可以看到这两个Header的值和WebServer发出的Last-Modified, Etag值完全一样;在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.

7、Last-Modified和Etag

分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败

分布式系统尽量关闭掉Etag(每台机器生成的etag都会不一样)

Last-Modified和ETags请求的http报头一起使用,服务器首先产生Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改,来决定文件是否继续缓存

过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。
  3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
  5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

注:

  1. Last-Modified和Etag头都是由WebServer发出的HttpReponse Header,WebServer应该同时支持这两种头。
  2. WebServer发送完Last-Modified/Etag头给客户端后,客户端会缓存这些头;
  3. 客户端再次发起相同页面的请求时,将分别发送与Last-Modified/Etag对应的HttpRequestHeader:If-Modified-Since和If-None-Match。这两个Header的值和WebServer发出的Last-Modified,Etag值完全一样;
  4. 通过上述值到服务器端检查,判断文件是否继续缓存;
8、关于 Cache-Control: max-age=秒 和 Expires

Expires = 时间,HTTP 1.0 版本,缓存的载止时间,允许客户端在这个时间之前不去检查(发请求)

max-age = 秒,HTTP 1.1版本,资源在本地缓存多少秒。

如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖。

Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。

Expires =max-age “每次下载时的当前的request时间”

所以一旦重新下载的页面后,expires就重新计算一次,但last-modified不会变化

9、浏览器刷新

正常重新加载

按下刷新按钮或快捷键(在 MacOS 中是 Cmd R)会触发浏览器的“正常重新加载”(normal reload), 此时浏览器会执行一次 Conditional GET。 Cache-Control 等缓存头字段会被忽略,并且带If-None-Match, If-Modified-Since等头字段。 此时服务器总会收到一次 HTTP GET 请求。 在 Chrome 中按下刷新,浏览器还会带如下请求头:

Cache-Control:max-age=0

注意:在地址栏重新输入当前页面地址并按下回车也会当做刷新处理, 这意味着只有从新标签页或超链接打开时,才能观察到直接使用硬盘缓存的情况。

强制重新加载

在 Chrome 中按下 Cmd Shift R (MacOS)可以触发强制重新加载(Hard Reload), 此时包括页面本身在内的所有资源都不会使用缓存。 浏览器直接发送 HTTP 请求且不带任何条件请求字段。 在 Chrome 中强制刷新,浏览器还会带如下请求头:

Cache-Control: no-cache

Pragma: no-cache

如何让缓存的静态文件失效

一般我们在页面上引用很多js或者css文件,一旦请求过并且缓存在浏览器中的资源并没有失效,这个时候发现我们有个bug需要修改或者有新的东西需要发布,你要怎么办?有些人就说了,强制刷新下浏览器就好了,或者在请求的时候不返回304,直接返回新的资源内容,但是这样并不好操作,一是用户未必知道强制刷新或者清理缓存,二是我们只想在发布新的内容之后第一次用户的请求返回新的内容并缓存,后面还是走缓存;三是我们一般都会使用CDN,每次发布完之后还需要清理CDN缓存,很是麻烦。其实有一个最简单的办法就是在引用这些静态资源的时候加一个版本号即可,类似.../js/index.js?v=1.0这样的,如果修改了内容,那么只需要改一下版本号即可,浏览器自然会获取到新的内容。

欢迎关注 “后端老鸟” 公众号,接下来会发一系列的专题文章,包括Java、Python、Linux、SpringBoot、SpringCloud、Dubbo、算法、技术团队的管理等,还有各种脑图和学习资料,NFC技术、搜索技术、爬虫技术、推荐技术、音视频互动直播等,只要有时间我就会整理分享,敬请期待,现成的笔记、脑图和学习资料如果大家有需求也可以公众号留言提前获取。由于本人在所有团队中基本都处于攻坚和探路的角色,搞过的东西多,遇到的坑多,解决的问题也很多,欢迎大家加公众号进群一起交流学习。

,