浏览器缓存
多级缓存简介
用户请求服务器资源,不需要每次都向服务器请求,通过浏览器本地存储响应给用户,通过 nginx 可以控制浏览器是否要做缓存(一些主流的站点都会使用,直接发缓存给用户甚至不做服务端校验)。
CDN 缓存:用户和 CDN 越近越快。
Nginx 缓存:基于代理的缓存,把相同的请求从上游服务器拉去缓存,从而减少后端的并发量(nginx 本身对静态资源的性能是非常高的),比如对 url 请求的 html 缓存。当然对于时效性的数据会有方法更新的(数据分层:热点内容缓存到内存,一般数据缓存到硬盘)。
上游服务器缓存:不在 nginx 讲解范围之内,是成体系、庞大的业务结构。
用户浏览器缓存 → CDN 缓存 → Nginx 缓存 → 上游服务器缓存
京东 Web 端浏览器缓存的使用情况

其中分别显示了内存缓存和磁盘缓存。
浏览器内存管理做的都不是很好,因为上面的缓存很难清理的,想要释放内存就得关闭页面,清除内存也不及时,所以打开多标签页浏览器会占用很多内存。
其中可以看到内存中的基本是 0ms,磁盘的缓存是 1ms,有的是 100 多 ms。

js 类的延迟的很小,图片延迟高。
可能说的有点乱,下面具体配置会清晰很多。
浏览器的强制缓存和协商缓存
几乎所有的网站都会用浏览器缓存。
强制缓存:用户访问站点,站点根据日期把数据缓存到浏览器(不过期就能继续使用)。
协商缓存:查一下是否发生变化,不单纯认为本机的时间是正确的。或以文件的修改日期为标准。
上述只做简单了解,会很模糊,待会直接看具体配置即可。
协商缓存使用场景:(下图)客户端访问站点的 index.html,同时告知 nginx 的最后修改时间 last_modify:2022 5.28:12:12,浏览器拿到文件,下次访问还是会请求 index.html,但是会带上修改日期(可以写入请求头)。nginx 收到去找上游服务器的磁盘文件的修改日期(只看修改日期,不读数据),如果已经是最新的了,nginx 就返回一个状态码 304,浏览器直接从本地磁盘发给用户。

强制缓存使用场景:请求到 nginx,nginx 返回文件并且加入过期时间,假设 3 天,浏览器拉到本地,如果 3 天之内就直接返回缓存给用户。
两种缓存都可以配合使用,目的是减少用户的重复请求。
协商缓存优点:有效避免强制缓存的过期、缓存一直不失效等问题。
ssi、缓存配置可能会和 gzip 冲突。


在静态资源页强制刷新(C+T+R),状态码是 200 代表没有使用缓存。
使用普通刷新看到了状态码 304,说明使用了缓存。
如上图 Etag 和 Last-Modified 是 Nginx 处理静态文件时自动添加 的响应头。
这两个是协商缓存的(分别是缓存文件的精确哈希和缓存的最后修改日期)。
实际上只需要 nginx 的默认的协商缓存就可以应对大部分的情况了,但还是有一些特殊的需求需要强制缓存。
Nginx Etag、Last-Modified 具体配置(协商缓存配置)
如果想要关闭 etag,比如关闭对应站点的 etag,可以直接在 location 的配置添加 etag off。
# /etc/nginx/sites-available/test
server {
listen 80;
server_name test.168kaguyachi.site;
root /var/www/test;
index index.html;
location / {
etag off; # 此处是新加入的配置
rewrite ^/([0-9]+).html$ /index.html?page=$1 break;
try_files $uri $uri/ =404;
}
location ~*^/(js|css|images)/ {
}
}
重载入 nginx 看控制台,该站点已经失去了 etag 和 lastmodify 标签。

是否使用缓存是浏览器决定的,nginx 只能给建议,所以返回了 304:

文件修改日期还在,304 是 nginx 主动返回的(上面说过没有携带文件只是一个状态码)。
想要浏览器不用缓存:直接取消 last-modified 的发送即可,也可以让 nginx 不返回 304 的状态码。
清除 header 的方法:由于 nginx 是自动发送的 lastmodify,没有关闭的选项,但是可以清除这个请求头字段 Last-Modified。
方法1:自身不返回。在对应 location 或者 server 配置 add_header Last-Modified "";
# /etc/nginx/sites-available/test
server {
listen 80;
server_name test.168kaguyachi.site;
root /var/www/test;
index index.html;
location / {
etag off;
add_header Last-Modified "";
rewrite ^/([0-9]+).html$ /index.html?page=$1 break;
try_files $uri $uri/ =404;
}
location ~*^/(js|css|images)/ {
}
}
重载完需要等待一会,然后测试就有结果了。


可以看到状态码一直是 200,请求和响应都没有文件修改日期了(因为浏览器无法判断文件是否修改,无法返回缓存文件)。
方法2:不让浏览器判断。写 if_modified_since off;
# /etc/nginx/sites-available/test
server {
listen 80;
server_name test.168kaguyachi.site;
root /var/www/test;
index index.html;
location / {
etag off;
if_modified_since off;
#add_header Last-Modified "";
rewrite ^/([0-9]+).html$ /index.html?page=$1 break;
try_files $uri $uri/ =404;
}
location ~*^/(js|css|images)/ {
}
}
插入:在 vim 写中文空格不行,在外部文本编辑器可以,因为全角空格会自动转换为半角空格,如果找不到中文空格的位置了,可以使用 cat -A [文件] 查看文件的所有不可见字符,换行是 $,Tab 是 ^I,全角空格或特殊空白字符为 M-BM- 之类的乱码。
语法不高亮很可能是写错了。
配置完测试:

可以看到浏览器还是携带 If-Modified-Since 请求头,但是响应码还是 200,因为 nginx 忽略了这个标头。
etag、lastmodify、cache-control、expires 具体配置(强制缓存配置)
强制缓存向服务器发送请求主要是 cache-control 和 expires。
使用强制缓存需要关掉 etag 和浏览器的 if modified since 吗?不需要的,可以都开启,二者可以共存的。但是为了单独测试强制缓存需要关掉。
expires
直接配置 expires [时间] 即可比如 expires 300s;
直接配置在 server 或者 location 即可。
需要补充的是,expires 是比较老的 header,HTTP/1.1 更多的是 cache-control,不同的浏览器识别/处理 header 的时候是不一样的,chrome 首次访问(到新标签访问)会识别到 expires。
为什么提到上面的补充呢?因为在 chrome 测试 expires 配置时,控制台一直没有显示使用到了缓存,开新标签才会使用缓存,刷新之后就一直不用缓存了。
总之,是否使用缓存都是浏览器决定的,nginx 只能“提建议”。
这里确实说的有点麻烦了,expires 算是一个老东西了,但是为什么留到现在还在用呢,因为他有很多其他的功能,比如:
expires 1h;
等价于
add_header Cache-Control "max-age=3600";
add_header Expires "Thu, 16 Jul 2026 10:00:00 GMT";
当然这是深层次的理解了,现在涉及不到的。
cache-control
作为 header 标头配置对应的字段 add_header cache-control "";
其中常见的只有三个:

no-cache 是不直接使用。
no-store 是不缓存。
max-age 和 expires 很像,代表浏览器缓存文件的最大缓存时间。
# /etc/nginx/sites-available/test
server {
listen 80;
server_name test.168kaguyachi.site;
root /var/www/test;
index index.html;
location / {
# 协商缓存配置
#etag off;
#if_modified_since off;
#add_header Last-Modified "";
rewrite ^/([0-9]+).html$ /index.html?page=$1 break;
try_files $uri $uri/ =404;
# 强制缓存配置
expires 300s;
add_header cache-control "max-age: 300";
# 这里加不加空格都可以
# 习惯了HTTP数据包都加,所以这里加了空格(vim写英文空格哦)
}
location ~*^/(js|css|images)/ {
}
}
刷新可以看到有 Cache-Control 字段了。


浏览器默认会请求 cache-control:

返回了两个,可能是上游又给加了一个。
这个情况是关闭了协商缓存只开了强制缓存。
假设用户只是想刷新页面,但是浏览器还是拉取缓存数据,这是不合理的,所以强制缓存的主要场景用在不必要刷新的页面,那种实时反馈的就不行。
cache-control 优先级比 expires 高。
缓存配置一般都是在 location,达到精确配置的效果。
总结:两个缓存方式是用来组合使用的。用户首次打开链接,进入强制缓存逻辑,浏览器直接从缓存拉数据。配合 lastmodify 和 etag,用户强制刷新时,进入 etag 的逻辑,文件不变化时,nginx 返回 304,浏览器拉缓存(首次打开和页面刷新都使用了缓存~)。

至此客户端的缓存算是比较完整了。
浏览器缓存额外需要注意的事项和应用场景
这里的东西很难理解,因为涉及到了具体的业务场景,所以可以混个耳熟,有助于理解业务流程和业务逻辑。
如图:

要保持多台机器的文件修改日期相同。
想要完全禁用浏览器(客户端)缓存是不可能的,但可以让 url 随着请求变化加上特殊信息:版本号、特定哈希值等:/v1.01/account/adduser?tx=hash 请求不一样自然不适用缓存了。
3XX 的跳转地址不需要缓存。
js/css 可以通过追加版本号更新内容(可以观察到大网站的处理方式)。
可以针对并发大的接口进行缓存(一般会配置时效)。
Etag 是资源生成之后才能加的。
客户端不一定都是浏览器,APP 作为客户端的情况也很多,这个就要和前端对接了(开发)。
不联网也可以展示一些数据。
浏览器缓存可以减少流量消耗。
提前下发数据可以减少流量洪峰压力服务器的可能。
兜底数据可以在服务器压力大的时候用(比如只展示一个框架,点不进去内容,而非直接来一个大报错)。
临时缓存,退出就删。
固定缓存,长时间不更新,和兜底数据很像。