不打不相识之http缓存

初识http缓存

近日,发现我打包的js代码上传到服务器后,并没有更新。想到用ng做了代理,可能是ng缓存的问题,就查资料学习了一下http(1.1)缓存的东西。

1.相关术语:(约定req为请求头,res响应头,C客户端,S服务端)

1
2
3
4
5
6
7
8
9
10
11
// response Headers
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 04 Dec 2019 09:11:07 GMT
Content-Type: text/css
Vary: Accept-Encoding
Last-Modified: Wed, 04 Dec 2019 09:03:18 GMT
ETag: W/"5de77656-2340"
Expires: Wed, 04 Dec 2019 21:11:06 GMT
Cache-Control: max-age=43200
Content-Encoding: gzip
  • Expires :res中为资源过期时间

  • Last-Modified: res中为资源最近修改时间

  • ETag: res中资源的唯一标识符(hash算法生成)

  • If-Modified-Since : req中的资源最近修改时间

  • If-None-Match :req中的资源标识

  • Cache-Control : resreq中表示缓存策略

    1. req中常用指令
字段名称 说明
max-age= 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间
max-stale[=] C可接收一个已经过期的资源。设置一个可选的秒数,不接受超过给定时间的资源
min-fresh= C希望获取一个能在指定的秒数内保持其最新状态的res
no-cache 在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证
no-store 不缓存有关客户端请求或服务器响应的任何内容

​ 2. res中常用指令(req中重复的不列举,详见MDN)

字段名称 说明
public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容
private 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容
must-revalidate 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求
proxy-revalidate must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略
s-maxage= 覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它

其中Last-ModifiedIf-Modified-SinceETagIf-None-Match是在每次的res,req中配对使用的。

2. 常见缓存场景:(约定资源为app.js)

  1. Expires:
    • 过程

      1. C端请求app.js —-> S端。
      2. S端响应app.jsExpires —-> C端。(C端缓存app.js直到Expires)
      3. if 1 发生在Expires 前,会直接从缓存中取, else 2
      4. 重复以上
    • 优势

      相比于最原始的不带缓存的请求和相应,优势很明显,会直接从缓存中取,减少请求响应次数

    • 缺陷

      如果app.jsExpires 内发生了改变,C端呈现的资源不是最新的。

  1. Expires + Last-Modified:
    • 过程

      1. C端请求app.js —-> S

      2. S端响应app.jsExpires*, *Last-Modified —-> C端。(C端缓存app.js直到Expires,上次修改时间是 Last-Modified

        • if 1 发生在Expires 前,会直接从缓存中取(200)。

        • else C端请求S端,带上If-Modified-Since(等于上一次相应的Last-Modified

          • S端用req中的If-Modified-Sinceres中的 Last-Modified比较。
            • if 一致,响应 C端:你可以继续用本地缓存(304)
            • else,2
      3. 重复以上

    • 优势

      相比与只使用Expires,if app.js发生变化,可以更新缓存,C端呈现内容为最新,else 不会有新的res拉一次资源,直接读缓存

    • 缺陷

      Last-Modified精确到秒,实际中有很多一秒内会完成很多reqres,问题呼之欲出

      • Last-Modified内,app.js被修改多次,那么C端还是会从缓存中读,呈现内容不是最新
      • 假设处于vue-cli开发下,因为某种原因,代码实际没有修改,但CI/CD重复构建打包了文件,app.js变为了app01.js(build.js生成的不同版本hash名称),但代码只是名称变化,内容并不变化,却重新拉了一次资源
  1. Expires + Last-Modified + ETag:
    • 过程

      1. C端请求app.js —-> S

      2. S端响应app.jsExpires*, *Last-Modified ,ETag—-> C端。(C端缓存app.js直到Expires,上次修改时间是 Last-Modified,文件标识是ETag

        • if 1 发生在Expires 前,会直接从缓存中取(200)。

          • else C端请求S端,带上If-Modified-Since(等于上一次相应的Last-Modified)和If-None-Match(等于上一次相应的Etag
            • S端用req中的If-None-Matchres中的 Etag比较,忽略If-Modified-SinceLast-Modified的比较。(如果Etag变化,Last-Modified一定变化,充分条件)
            • if 一致,响应 C端:你可以继续用本地缓存(304)
            • else,2
      3. 重复以上

    • 优势

      相较于上一种,使得资源变更的验证更加严格。

    • 缺陷

      让我们设想这种情况,我们频繁的修改app.js,打包构建,处于某种原因,我们并不想C端呈现最新的app.js,而是一段时间后再读取最新的,显然还达不到我们的要求

  1. Expires + Last-Modified + ETag + Cache-Control :
    • 过程

      1. C端请求app.js —-> S

      2. S端响应app.jsExpires*, *Last-Modified ,ETag, *Cache-Control:max-age=43200 —-> C端。(C端发现带有 Cache-Control:max-age=43200,忽略Expires,记住Last-Modified ,ETag

        • if 1 发生在(req发生的时间+ 12h(43200s)),会直接从缓存中取(200)。

          • else C端请求S端,带上If-Modified-Since(等于上一次相应的Last-Modified)和If-None-Match(等于上一次相应的Etag
            • S端用req中的If-None-Matchres中的 Etag比较,忽略If-Modified-SinceLast-Modified的比较。(如果Etag变化,Last-Modified一定变化,充分条件)
            • if 一致,响应 C端:你可以继续用本地缓存(304)
            • else,2
      3. 重复以上

    • 优势

      达到了我们上个方案达不到的效果

    • 缺陷?

      C端无法主动知道S端上我们请求的资源变化,只能被动的从res中得知,这算缺陷吗?我更多的认为是基于某种安全策略,所以这样规定。

3. 常见问题:(约定资源为app.js)

  1. 如何设置不缓存?

    • ng配置如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 还有多种设置方法,举例一种
      // 重启ng不一定立即生效
      location / {
      access_log /data/nginx/log/xxx.log api;

      root /home/www/html;

      if ($request_filename ~ .*\.(htm|html)$)

      {
      add_header Cache-Control no-cache;
      }
      }
    • 打包html设置meta标签如下

      1
      2
      3
      <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
      <meta http-equiv="Pragma" content="no-cache" />
      <meta http-equiv="Expires" content="0" />

      meta是用来在HTML文档中模拟HTTP协议的响应头报文。meta 标签用于网页的与中,meta 标签的用处很多。meta 的属性有两种:name和http-equiv。name属性主要用于描述网页,对应于content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。这其中最重要的是description(站点在搜索引擎上的描述)和keywords(分类关键词),所以应该给每页加一个meta值。

  2. 如何清理缓存?

  3. 如果发生缓存错误,检查的步骤?

    1. 检查是否传错文件夹

      • 打开项目打包后的js,检查app.js文件名。
      • 打开浏览器控制台Network,勾选js,F5刷新后找到对应的app.xxx.js,比较。如果你发现名称不一样,而且res头部 Last-Modified也不对,那么大概率你传错文件夹了。
    2. 检查是否正确更新

      • 记录现阶段 ETag
      • 重新上传后刷新,比较两次 ETag是否一致
    3. 检查是否正确配置ng等Server

    4. 梳理构建部署步骤,逐步检查(只能帮你到这啦)

只是http(1.1)的部分常见场景,目前到这里已经足够,咱得一步一步来,切勿囫囵吞枣~

思考有限,难免出现疏漏,欢迎诸位指出,集思广益。

reference
  1. 《面试精选之http缓存》 https://juejin.im/post/5b3c87386fb9a04f9a5cb037#heading-2
  2. 《MDN - Cache-Control》https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
  3. 《浅谈http中的Cache-Control》https://blog.csdn.net/u012375924/article/details/82806617
  4. 《Nginx缓存配置及nginx ngx_cache_purge模块的使用》https://www.cnblogs.com/Eivll0m/p/4921829.html

评论