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

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 :
res,req中表示缓存策略
req中常用指令
| 字段名称 | 说明 |
|---|---|
| max-age= | 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间 |
| max-stale[=] | C可接收一个已经过期的资源。设置一个可选的秒数,不接受超过给定时间的资源 |
| min-fresh= | C希望获取一个能在指定的秒数内保持其最新状态的res |
| no-cache | 在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证 |
| no-store | 不缓存有关客户端请求或服务器响应的任何内容 |
res中常用指令(req中重复的不列举,详见MDN)
| 字段名称 | 说明 |
|---|---|
| public | 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容 |
| private | 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容 |
| must-revalidate | 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求 |
| proxy-revalidate | 与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略 |
| s-maxage= | 覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它 |
其中Last-Modified与If-Modified-Since,ETag与If-None-Match是在每次的res,req中配对使用的。
2. 常见缓存场景:(约定资源为app.js)
-
Expires:
-
过程
C端请求app.js ---->S端。S端响应app.js 与Expires ---->C端。(C端缓存app.js直到Expires)- if 1 发生在Expires 前,会直接从缓存中取, else 2
- 重复以上
-
优势
相比于最原始的不带缓存的请求和相应,优势很明显,会直接从缓存中取,减少请求响应次数
-
缺陷
如果app.js在Expires 内发生了改变,
C端呈现的资源不是最新的。
-
-
Expires + Last-Modified:
-
过程
C端请求app.js ---->S端S端响应app.js 与Expires, 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-Since和res中的 Last-Modified比较。- if 一致,响应
C端:你可以继续用本地缓存(304) - else,2
- if 一致,响应
- 重复以上
-
优势
相比与只使用Expires,if app.js发生变化,可以更新缓存,
C端呈现内容为最新,else 不会有新的res拉一次资源,直接读缓存 -
缺陷
Last-Modified精确到秒,实际中有很多一秒内会完成很多
req和res,问题呼之欲出- 在 Last-Modified内,app.js被修改多次,那么
C端还是会从缓存中读,呈现内容不是最新 - 假设处于
vue-cli开发下,因为某种原因,代码实际没有修改,但CI/CD重复构建打包了文件,app.js变为了app01.js(build.js生成的不同版本hash名称),但代码只是名称变化,内容并不变化,却重新拉了一次资源
- 在 Last-Modified内,app.js被修改多次,那么
-
-
Expires + Last-Modified + ETag:
-
过程
C端请求app.js ---->S端S端响应app.js 与Expires, 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-Match和res中的 Etag比较,忽略掉If-Modified-Since和Last-Modified的比较。(如果Etag变化,Last-Modified一定变化,充分条件)- if 一致,响应
C端:你可以继续用本地缓存(304) - else,2
- else
- if 1 发生在Expires 前,会直接从缓存中取(200)。
- 重复以上
-
优势
相较于上一种,使得资源变更的验证更加严格。
-
缺陷
让我们设想这种情况,我们频繁的修改app.js,打包构建,处于某种原因,我们并不想
C端呈现最新的app.js,而是一段时间后再读取最新的,显然还达不到我们的要求
-
-
Expires + Last-Modified + ETag + Cache-Control :
-
过程
C端请求app.js ---->S端S端响应app.js 与Expires, 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-Match和res中的 Etag比较,忽略掉If-Modified-Since和Last-Modified的比较。(如果Etag变化,Last-Modified一定变化,充分条件)- if 一致,响应
C端:你可以继续用本地缓存(304) - else,2
- else
- if 1 发生在(
- 重复以上
-
优势
达到了我们上个方案达不到的效果
-
缺陷?
C端无法主动知道S端上我们请求的资源变化,只能被动的从res中得知,这算缺陷吗?
-
3. 常见问题:(约定资源为app.js)
-
如何设置不缓存?
-
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值。
-
-
如何清理缓存?
-
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。
-
-
如果发生缓存错误,检查的步骤?
- 检查是否传错文件夹
- 打开项目打包后的js,检查app.js文件名。
- 打开浏览器控制台
Network,勾选js,F5刷新后找到对应的app.xxx.js,比较。如果你发现名称不一样,而且res头部 Last-Modified也不对,那么大概率你传错文件夹了。
- 检查是否正确更新
- 记录现阶段 ETag
- 重新上传后刷新,比较两次 ETag是否一致
- 检查是否正确配置ng等
Server - 梳理构建部署步骤,逐步检查(只能帮你到这啦)
- 检查是否传错文件夹
只是http(1.1)的部分常见场景,目前到这里已经足够,咱得一步一步来,切勿囫囵吞枣~
思考有限,难免出现疏漏,欢迎诸位指出,集思广益。
欢迎关注我的公众号《web工程师的自我修养》,一起交流学习共勉~
reference
- 《面试精选之http缓存》 juejin.cn/post/684490…
- 《MDN - Cache-Control》developer.mozilla.org/zh-CN/docs/…
- 《浅谈http中的Cache-Control》blog.csdn.net/u012375924/…
- 《Nginx缓存配置及nginx ngx_cache_purge模块的使用》www.cnblogs.com/Eivll0m/p/4…