不打不相识之http缓存
初识http
缓存
近日,发现我打包的js
代码上传到服务器后,并没有更新。想到用ng
做了代理,可能是ng
缓存的问题,就查资料学习了一下http
(1.1)缓存的东西。
1.相关术语:(约定req
为请求头,res
响应头,C
客户端,S
服务端)
1 | // response Headers |
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 | 不缓存有关客户端请求或服务器响应的任何内容 |
2. 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
重复以上
优势
相较于上一种,使得资源变更的验证更加严格。
缺陷
让我们设想这种情况,我们频繁的修改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
重复以上
优势
达到了我们上个方案达不到的效果
缺陷?
C
端无法主动知道S
端上我们请求的资源变化,只能被动的从res
中得知,这算缺陷吗?我更多的认为是基于某种安全策略,所以这样规定。
3. 常见问题:(约定资源为app.js)
如何设置不缓存?
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值。
如何清理缓存?
Nginx
企业版提供了purger功能,对于社区版Nginx可以考虑使用
ngx_cache_purge`(https://github.com/FRiCKLE/ngx_cache_purge)模块进行清理缓存。(该方法最好限制其访问权限,如只允许内网可以访问或者需要密码才能访问)1
2
3
4
5
6
7
8
9location ~ /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)的部分常见场景,目前到这里已经足够,咱得一步一步来,切勿囫囵吞枣~
思考有限,难免出现疏漏,欢迎诸位指出,集思广益。
reference
- 《面试精选之http缓存》 https://juejin.im/post/5b3c87386fb9a04f9a5cb037#heading-2
- 《MDN - Cache-Control》https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
- 《浅谈http中的Cache-Control》https://blog.csdn.net/u012375924/article/details/82806617
- 《Nginx缓存配置及nginx ngx_cache_purge模块的使用》https://www.cnblogs.com/Eivll0m/p/4921829.html