HTTP Cache Control 响应头
- Cache Control 字段告诉客户端,我这个 HTTP 的 Response 可不可以缓存,可以如何被缓存;
Cache Control 字段的取值
- private:只有客户端可以缓存;
- public:客户端和代理服务都可以缓存,代理服务指的是 Nginx、CDN、中间的正向代理服务器;
- max-age=xxx:指明从客户端收到响应开始,内容可以被缓存的时间;
- no-cache:客户端可以缓存,但是使用缓存前还要向服务端验证一下,缓存能用不能用;
- no-store:不缓存请求的任何返回内容;
no-cache 详解
客户端在使用缓存的时候,还会向服务端发请求验证一下,我客户端缓存的内容的有效性,验证的依据有如下几种:
- ETag & If-None-Match:ETag 是资源唯一标识,一般都是将请求的资源的内容做一个 MD5,在第一次返回内容的时候,加上 ETag 标识,一起返回给浏览器,浏览器会存储下这个 ETag,有效性判断就是把之前缓存下来的 ETag 值,一起发送给服务器,服务器会把收到的 ETag 和本地的 ETag 做比较,若比较是一致的,就返回一个 304(Not Modified),客户端收到 304 就知道自己缓存的内容是有效的,直接使用即可;If-None-Match 客户端第二次发送相同的请求时,会带上 If-None-Match 字段,其值就是第一次请求时,服务器返回的 ETag 的值;
- Last-Modified & If-Modified-Since:Last-Modified 这是服务端响应时给出的字段,其值是资源最后被修改的时间;If-Modified-Since 是客户端第二次发送相同请求时带的字段,其值是第一次服务器返回的 Last-Modified,服务器收到请求后,判断 If-Modified-Since 的值如果比服务器中资源的 Last-Modified 早,那么客户端中缓存的内容就已经失效了;
注:ETag & If-None-Match 和 Last-Modified & If-Modified-Since 这两种判断客户端缓存有效性的机制是互斥的;
浏览器的三种刷新方式
回车刷新或 a 链接
- 看 Cache-Control 对应的 max-age 是否任然有效,有效则直接从客户端缓存中获取;
- 若 Cache-Control 中指定了 no-cache,则进入缓存协商逻辑;
F5 刷新或 command + R
- 去掉 Cache-Control 中的 max-age 或直接设置 max-age 为 0,然后进入缓存协商逻辑;
Crtl + F5 或 command + shift + R
- 去掉 Cache-Control 和协商头(Etag 和 Last-Modified),强制刷新,强制把请求发到服务器;
协商机制
- 发送请求到服务器,在服务器端比较 Etag 和 Last-Modified,若服务器端比较的结果是数据没变化,返回 304,不返回数据,否则返回 200,返回新的数据;
服务端对动态请求的处理
- 动态请求就是 ajax 请求;
- 服务器对动态请求的处理方式一般是,在 Cache-Control 中返回 no-cache,就是说,客户端可以缓存数据,但是每次在使用缓存数据的时候,还是要发个请求给服务端,走一次协商机制;
Tomcat 对动态请求缓存的默认处理
- 默认情况下,浏览器请求直接发送给 SpringBoot 的 Tomcat,响应头里是没有 Cache-Control 头的,所以每次请求都不会用到缓存;
CDN 自定义缓存策略
- CDN 作为客户端的服务端,服务端的客户端,其缓存策略十分重要;
可自定义目录过期时间
- 针对目录,比如 /resources/item/get 源站可以定义数据过期时间是 1 年,但是 CDN 可以设定存储在自己这的数据过期时间,不受服务器的影响,比如设置成 1 小时;
可自定义后缀名的过期时间
- 比如针对 .html, .jpg, .png 设置不同的过期时间;
可自定义对应权重
- 比如针对后缀名的权重比针对目录的权重高,那么针对后缀名设置的过期时间的优先级就比针对目录的过期时间高;
可通过界面或 API 强制 CDN 对应的目录刷新
- 就是强制回源,拿去最新的内容,这个操作不一定能保证成功,而且有数量上的限制;
静态资源部署策略
突然发布新版本,但是 CDN 中的失效时间还没到导致用户用的还是老功能怎么办?
- 比如 a.js 文件,部署在 Nginx 上不叫 a.js,而是把 a.js 的内容做哈希,哈希值为 xxx,然后部署到 Nginx 上的 a.js 叫 xxx.js;在 Nginx 上的 html 在引 a.js 的时候引的也是 xxx.js;
- 有新的 a.js 要部署的时候,新的文件哈希后叫 yyy.js,先把 yyy.js 部署到 Nginx 上,然后预热到 CDN 上,此时 CDN 上有两个 a.js 的版本,xxx.js 和 yyy.js;
- 然后把新的 html 部署到 Nginx 上,然后预热到 CDN 上,就部署完了;
动态请求可以上 CDN 吗?
- 可以;
- 动态请求可以静态化成 JSON 资源推送到 CDN 上;
- 客户端从 CDN 拿到静态 JSON 后,先展示在页面上,然后向后端发一个 ajax,这个 ajax 只对比两边资源的版本号,静态化成 JSON 的数据是有个版本号的,每次更新这个资源的任何信息,版本号加 1,生成新的 JSON 文件,推到 CDN 上去;如果数据涉黄需要紧急下架,这个 ajax 可以感知到,然后从页面上下架相关信息;
- 如果客户端在 CDN 上获取的静态 JSON 是老的,再发个 ajax 请求,并且覆盖掉界面上的一些内容,虽然页面上会闪一下,但在 CDN 推送失败率很低的情况下都是可以接收的;
- 也可以通过跑批,推送 CDN 内容;