初识http缓存

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

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

// 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 不缓存有关客户端请求或服务器响应的任何内容
  1. 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中配对使用的。html

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

  1. Expires:
    • 过程vue

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

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

    • 缺陷github

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

  2. Expires + Last-Modified:
    • 过程面试

      1. C端请求app.js ----> S
      2. S端响应app.jsExpiresLast-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拉一次资源,直接读缓存vue-cli

    • 缺陷

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

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

      1. C端请求app.js ----> S
      2. S端响应app.jsExpiresLast-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,而是一段时间后再读取最新的,显然还达不到咱们的要求

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

      1. C端请求app.js ----> S
      2. S端响应app.jsExpiresLast-Modified ,ETagCache-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配置以下:

      // 还有多种设置方法,举例一种
      // 重启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标签以下

      <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. 如何清理缓存?

    • Nginx企业版提供了purger功能,对于社区版Nginx能够考虑使用ngx_cache_purge(该方法最好限制其访问权限,如只容许内网能够访问或者须要密码才能访问)github.com/FRiCKLE/ngx…

      location ~ /purge(/.*) {
      
        allow 127.0.0.1;
      
        deny all;
      
        proxy_cache_purge cache$1$is_args$args;
      
       }
      复制代码

      PS: 相似宝塔面板这种Ng都自动安装了ngx_cache_purge模块,如何设置详见下方参考。

    • 找到缓存文件夹,直接kill。

  3. 若是发生缓存错误,检查的步骤?

    1. 检查是否传错文件夹
      • 打开项目打包后的js,检查app.js文件名。
      • 打开浏览器控制台Network,勾选js,F5刷新后找到对应的app.xxx.js,比较。若是你发现名称不同,并且res头部 Last-Modified也不对,那么大几率你传错文件夹了。
    2. 检查是否正确更新
      • 记录现阶段 ETag
      • 从新上传后刷新,比较两次 ETag是否一致
    3. 检查是否正确配置ng等Server
    4. 梳理构建部署步骤,逐步检查(只能帮你到这啦)

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

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

欢迎关注个人公众号《web工程师的自我修养》,一块儿交流学习共勉~

reference
  1. 《面试精选之http缓存》 juejin.im/post/5b3c87…
  2. 《MDN - Cache-Control》developer.mozilla.org/zh-CN/docs/…
  3. 《浅谈http中的Cache-Control》blog.csdn.net/u012375924/…
  4. 《Nginx缓存配置及nginx ngx_cache_purge模块的使用》www.cnblogs.com/Eivll0m/p/4…
相关文章
相关标签/搜索