http权威指南
Http缓存处理流程
Http本身有一系列机制,可以保持缓存足够新鲜,这一整套机制可以称为文档过期和服务器再验证。

文档过期
当浏览器向服务器发出请求时,服务器已经在响应里添加Max-Age(1.1)或Expires(1.0),来表示返回对象的过期时间。其中,Max-Age表示过期相对时间、精确到秒,Expires表示过期的绝对时间。
一般情况下,我们是使用Max-Age(1.1),因为本地的时间跟服务器不一致,假如我们的时间比服务器的晚一两年时间,很可能我们返回的Expires一直都是过期,导致我们一直需要从服务器抓取文件。
另外,Max-Age是Http1.1协议里的,Expires是Http1.0协议的,所以浏览器(现在绝大多数浏览器都已支持Http1.1)发出请求时,如果同时带着Max-Age和Expires请求头,根据浏览器是否支持Http1.1分为两种情况:
- 如果浏览器支持Http1.1,那么会优先判断当前日期是否超过上一次Date和Max-Age,不会去考虑Expires
- 如果浏览器只支持Http1.0,那么会判断当前本地日期是否超过Expires
如果超过Max-Age或Expires,就视为文档过期,需要进行新鲜度检测。
服务器再验证
如果浏览器本地缓存文件已过期,意味着我们需要去服务器上判断本地缓存是否最新文件,如果文件内容已被更改就从服务器上重新抓取文件下来。整个验证过程我们可以称其为服务器再验证,包含了两种验证器:Last-Modified和E-Tag。
Last-Modified是指文件的最后修改时间,它不能比Date晚。我们可以利用响应的这个字段来判断本地文档是否已过期,但是这验证器有个问题是:
- 如果一个文件同一秒内被更改多次,虽然内容已经改变了,但是还是会被视为同一个文件
- 如果服务器上多次tough这个文件,即使没有修改实质性内容,但是还是会被视为不同的文件
E-Tag是指文件的实体标签,有点类似文件的MD值。我们在响应中获取该值,在下一次请求里If-none-match会自动带上该值去服务器验证。
- 如果我们的服务器启用了负载均衡,可能会导致同一文件在不同服务器下的E-Tag值不同,导致缓存失效。如果需要取消E-Tag的话,可以通过设置FileETag None,不让服务器返回E-Tag(Apache服务器)
缓存处理流程
结合上一节的文档验证,我们可以将缓存处理分为四个场景:
- 缓存命中
- 缓存未命中
- 缓存再验证命中
- 对象被删除

当我们浏览器向服务器发起请求时,会先检测本地请求的Max-Age或Expires,如果没有超过Max-Age或者Expires的话,那么就不会发起请求,我们可以直接使用本地缓存。这种情况我们可以称为缓存命中。
那如果我们的Max-Age或Expires已经过时了,那我们会把本地缓存视为不新鲜,我们需要向服务器发起新鲜度检测。此时我们会抓取上一次响应中的E-tag或者Last-Modified作为请求中的If-None-Match或If-Modified-Since跟服务器上的文件进行校验,如果一致,那么视为我们本地缓存还是最新的,服务器会返回304 Not Modified进行响应,并更新Max-Age或Expires。同时,浏览器也直接返回本地缓存。这种情况我们可以称为缓存再验证命中。
那么,如果我们请求里的If-None-Match或If-Modified-Since和服务器上的E-tag或者Last-Modified不一致,也就意味着其实本地缓存已经过时了,那么就会从服务器上抓取最新的文件至本地,进行更新。同时服务器会返回HTTP 200 OK的响应。这种情况我们可以称为缓存未命中。
如果我们访问的服务器对象被删除了,服务器会返回 HTTP 404 Not Found响应,同时,相应的缓存文件也会被删除。
补充
强弱验证
再验证的验证方式有分为两种,强验证和弱验证。
-
强验证是指服务器文件发生了任何改动,都会强制更新缓存。如果服务器想废弃之前的缓存文件或者使用严格的版本控制时就可以使用强验证方式。
-
弱验证是指当服务器文件发生了实质性的改动时,才会将其更新缓存。如果我们没办法完全分析两个文件的差异(确定两个文件的唯一性)或者允许文件进行一些无关的动态更改时可以使用弱验证。
-
Last-Modified默认是弱验证,E-Tag默认是强验证,如果要启用弱验证的话,需要添加W/前缀
Cache-Control
- Max-Age=num(s),设置最大缓存时间;
- public,可以被中间代理缓存,能被多用户共享;
- private,缓存不能被中间代理缓存,只能被单用户使用,默认都是private;
- no-cache,允许本地缓存,但是每次使用之前都进行服务器再验证。适用于时效性比较高的应用;
- no-store,不允许任何地方进行缓存,适用于安全性比较高的应用
基于CDN的资源缓存方案
CDN与反向代理
CDN全称是内容分发网络,通过在网络各处放置节点服务器,在现有的Internet架构里新增一层新的网络架构,将网站的内容推送到最接近用户的网络“边缘”,使得用户可以就近取得所需的内容,大大优化链路性能,加速网络传输,减少网络延迟。
引入CDN之后,典型的用户访问网络流程图如下:

- 当用户发起请求时,域名会通过DNS解析服务最终定位到CDN域名服务器,接着会返回一个全局CDN负载均衡服务器IP地址,浏览器会重定向到对应IP地址。
- CDN负载均衡服务器(反向代理)会根据用户的IP地址,以及用户请求的内容,选择一台用户所属区域内的区域负载均衡服务器,将用户请求转发至该区域服务器。
- 区域负载均衡服务器会结合用户区域、请求内容以及服务器情况(根据实际的负载均衡策略),返回一台缓存服务器的IP地址。
- 全局CDN服务器会返回缓存服务器IP地址给用户。
- 浏览器将请求重定向至缓存服务器,缓存服务器将请求结果返回浏览器。如果缓存服务器上没有对应内容,则逐步追溯至源服务器将内容拉取至本机,并返回给浏览器。
HTML/CSS/JS/Image的CDN缓存方案
如果加上CDN之后,整个缓存方案应该是这样子的:
-
html
一般页面(包括JSP/ASP等)包含了所有的组件,实时性要求较高,所以设置Cache-control是no-cache,默认本地文档过期,要求每次请求都要到服务器上进行再验证;或者设置max-age为很短的周期:可能是几分钟左右。但如果界面实时性要求不高,就可以设置一个较长的max-age时间
-
css/js
一般css和js更新速度较慢,特别是涉及一些公共库(jquery/react等),需要设置一个长缓存。css和js一般都是通过url的形式外联进html,所以当我们需要更新缓存时,直接更新对应的资源名称(引用的url地址)即可。可以根据资源生成的MD5值/时间戳/版本号,来更新对应资源名称,建议用MD5值,因为现在大部分构建工具(Webpack)都可以根据资源的MD5值生成对应的hash。
-
image image需设置为长缓存,一般都是设置随机名称即可。