Nginx: 缓存, 不缓存特定内容和缓存失效降低上游压力策略及其配置示例
概述
- 在负载均衡的过程中,有一个比较重要的概念,就是缓存
- 利用缓存可以很好协调Nginx在客户端和上游服务器之间的速度不匹配的矛盾
- 从而很好的解决整体系统的响应速度
- 如果用户需要通过Nginx获取某一些内容的时候,发起一个request请求
- 这个请求到了Nginx之后,静态内容会直接返回,动态内容会转发至应用程序服务器
- 后端应用服务器再处理完这样一个请求器,会封装响应包体,再返回给我们的Nginx
- Nginx 收到 response 包体之后,再一并的把这些内容返回给我们的用户
- 这里的问题在于,Nginx 处理能力很强,但是上游的服务器它是处理动态内容的
- 而且在后端通常情况下,可能还有一些数据库服务器
- 它还需要将请求再去找数据库服务器获取内容之后,就这段请求处理的时候是非常慢
- 因此就有一个矛盾,当用户发起请求的时候,很快到达Nginx
- 但这个内容返回之前,可能需要经经过长的时时间
- 用户体验并非取决于前一段,而是取决于整个系统中最慢的那一段
- 想要提升用户体验的话,缓存就做了这样一个思路
- Nginx 提供一些缓存能力
- 当第一次request的请求到达Nginx的时候
- Nginx可以依据一定的缓存策略发送请求给应用服务器
- 当应用服务器处理完请求封装包底返回给Nginx的时候
- Nginx 在返回给客户端之前,它会依据缓存的策略
- 将某一部分信息先缓存到Nginx自身的cache中(Nginx 服务器的内存中开辟的一段空间)
- 比如, 请求一个图片,或某一些动态内容,都可以把它缓存到这样一个cache中
- 当用户第一次请求的时候,可能在缓存中是没有对应的这样一些内容的
- 这个时候Nginx必须要发去请求到后端应用服务器获取
- 但是当第一次获取到这些内容之后,会把这些内容放到cache中
- 等到下一个用户请求的时候,发现你这个URL请求的是同样的内容
- Nginx不会再向后端应用服务器发送请求
- 而是直接将将自身内存中的cache中的一些信息,直接返回给客户端中
- 这对于提升整个用户体验来说是非常关键的
- 在互联网时代有一句话叫缓存为王,尤其是并发量很大的场景下
- 这已经不是一个策略问题,而是一个必须要做的事情
缓存分类
1 )客户端缓存
- 所谓客户端缓存,就是指我们的用户的浏览器
- 那客户端我们对于用户来说,它的浏览器就是一个客户端,它发送请求到Nginx服务器
- 我们先来看一下客户端缓存优点:直接在本地获取内容,没有网络消耗, 响应是最快的
- 用户和Nginx之间通常是经过运营商的网络的,这样很好的避免了这样一些网络请求
- 这样,它可以极大的减少我们网络消耗的开销
- 缺点也是很明显,当用户只启用了客户端缓存的时候,只有这个用户可以使用这些缓存
- 而且这些缓存也有可能造成一些数据不一致的问题
2 )服务端缓存
- 它的优势相比客户端来说,它就是对所有的用户都能够生效
- 另外一点它还可以有效的去降低上游应用服务器的一个压力,缓解并发能力差的问题
- 缺点就是仍然有一些网络消耗
3 )最佳实践
- 同时启用客户端缓存和服务端缓存
相关指令
1 )proxy_cache 指令
- 语法:proxy_cache zone | off;
- zone 是共享内存中开辟的空间
- 默认值:proxy_cache off;
- 上下文:http、server、location
2 )proxy_cache_path 指令
-
语法:proxy_cache_path path [ level=levels ] [ use_temp_path=on/off]
keys_zone=name:size [inactive=time] [max_size=size]
[manager_files=number] [manager_sleep=time]
[manager_threshold=time] [loader_files=number]
[loader_sleep=time] [loader_threshold=time]
[purger=on|off] [purger_files=number]
[purger_sleep=time] [purger_threshold=time]; -
默认值:proxy_cache_path off;
-
上下文:http
-
参数含义
可选参数 含义 path 缓存文件的存放路径 level path的目录层级 use_temp_path off直接使用path路径;on使用proxy_temp_path路径 keys_zone name是共享内存名称;size是共享内存大小 inactive 在指定时间内没有被访问缓存会被清理;默认10分钟 max_size 设定最大的缓存文件大小,超过将由CM(Cache Manager)清理 manager_files CM清理一次缓存文件,最大清理文件数;默认100 manager_sleep CM清理一次后进程的休眠时间;默认200毫秒 manager_threshold CM清理一次最长耗时;默认50毫秒 loader_files CL(Cache Loader) 载入文件到共享内存,每批最多文件数;默认100 loader_sleep CL加载缓存文件到内存后,进程休眠时间;默认200毫秒 loader_threshold CL每次载入文件到共享内存的最大耗时;默认50毫秒
3 )proxy_cache_key 指令
- 决定缓存内容,保存的key信息
- 语法:proxy_cache_key string;
- 默认值:proxy_cache_key $scheme $proxy_host $request_uri
- 上下文:http、server、location
4 )proxy_cache_valid 指令
- 语法:proxy_cache_valid [ code … ] time;
- 这里的 code 是 http 的状态码
- 默认值:-
- 上下文:http、server、location
- 配置示例:proxy_cache_valid 60m; # 只对200、301、302响应码缓存
5 )upstream_cache_status 变量
- 变量名:upstream_cache_status
- MISS: 未命中缓存
- HIT: 命中缓存
- EXPIRED: 缓存过期
- STALE: 命中了陈旧缓存
- REVALIDDATED: Nginx验证陈旧缓存依然有效
- UPDATING: 内容陈旧,但正在更新
- BYPASS: 响应从原始服务器获取
缓存基本指令配置示例
1 )环境准备
-
Nginx 服务器准备:192.168.184.240
proxy_cache_path /opt/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=32g inactive=60m use_temp_path=off; upstream cache_server { server 192.168.184.20:1010; server 192.168.184.20:1011; } server { listen 80; server_name cache.baidu.com; location / { proxy_cache cache zone; # 开启缓存功能 proxy_cache_valid 200 5m; # 对200状态码的缓存 5分钟 proxy_cache_key $scheme$proxy_host$request_uri # 这个是缓存文件中的 key 值,可以自行修改 add_header Nginx-Cache-Status "$upstream cache status"; # 缓存是否命中的变量带到响应头上 proxy_pass http://cache server; } }
- levels=2:2 第一个2表示目录命名时字符串长度,第二个2表示目录的层次
- keys_zone=cache_zone:30m 这里的30m是可用的共享内存大小, 1m可缓存8000个key
- inactive=60m 60分钟内缓存没有被使用则清理
-
应用服务器准备:192.168.184.20
server { listen 1010; root /opt/html/1010; add_header X-Accel-Expires 5 # 告诉下游Nginx 只能缓存 5s location / { index index.html index.htm; } } server { listen 1011; root /opt/html/1011; add_header X-Accel-Expires 5 location / { index index.html index.htm; } }
- 文件:/opt/html/1010/cache.txt 内容为:cc-1010
- 文件:/opt/html/1011/cache.txt 内容为:cc-1011
-
客户端访问测试
- 已经配置nginx服务器hosts 为 cache.baidu.com,可直接访问
- 多次执行,$
curl cache.baidu.com/cache.txt
可知,只会分配到一台服务器上 - 执行 $
curl cache.baidu.com/cache.txt -I
- 重启nginx后,也就是第一次访问,Nginx-Cache-Status: MISS,值是 MISS
- 到了第二次,这个值就是 HIT
- 在 cache_temp/ 目录中会发现一个两字符的两层级目录
- 例如:e4/97 在这个目录下还有一个hash文件,就是cache.txt的缓存文件
- 等到5s以后再次访问,就会失效, 值变成 EXPIRED
配置 Nginx 不缓存特定内容
1 )proxy_no_cache 指令
- 对于特定场景不去缓存,如购物网站,商品数量不缓存
- 语法: proxy_no_cache string;
- 引用某一个特定变量,有值则不缓存
- 默认值: -
- 上下文:http、server、location
2 )proxy_cache_bypass 指令
- 配置不使用缓存,而是路由到上游服务器
- 语法: proxy_cache_bypass string;
- 默认值: -
- 上下文:http、server、location
3 )Nginx 配置 proxy_cache示例
proxy_cache_path /opt/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=32g inactive=60m use_temp_path=off;
upstream cache_server {
server 192.168.184.20:1010;
server 192.168.184.20:1011;
}
server {
listen 80;
server_name cache.baidu.com;
# 注意,这里
if ($request_uri ~ \.(txt | text)$ ) {
set $cookie_name "no cache";
}
location / {
proxy_cache cache zone; # 开启缓存功能
# 注意这里的添加 --- 开始
proxy_no_cache $cookie_name; # 这里引用 cookie_name
# 注意这里的添加 --- 结束
proxy_cache_valid 200 5m; # 对200状态码的缓存 5分钟
proxy_cache_key $scheme$proxy_host$request_uri # 这个是缓存文件中的 key 值,可以自行修改
add_header Nginx-Cache-Status "$upstream cache status"; # 缓存是否命中的变量带到响应头上
proxy_pass http://cache server;
}
}
- 这类请求,会对 txt/text 的文件不适用缓存, 都是 MISS
- 对其他文件使用缓存,缓存的标志值是:MISS / HIT / EXPIRED
- 不再做测试说明
缓存失效降低上游压力策略
- 当缓存失效的时候,如何去降低请求直接穿透Nginx从而导致上游应用服务器压力大的这样一个场景
- 这里有两种处理机制:
- 合并源请求
- 启用陈旧缓存
1 )合并源请求
- 当用户发送很多并发请求到Nginx的时候,假定这个时候Nginx重启了
- 重启之后,假如缓存整体失效,导致请求经过 Nginx 直接透传给后端的应用服务器
- 当并发请求量很大的情形下,由于Nginx 处理能力和后端处理能力不匹配,可能会导致很多的请求穿透Nginx到达后端
- 这些并发请求很可能会直接导致上游的服务器瘫痪掉
- 为了避免这种情形的发生, 对于Nginx 也采取了一些机制
- proxy_cache_lock 指令
- 收到的多个请求会有一个请求进入应用服务器,其他请求原地等待缓存
- 语法:proxy_cache_lock on | off;
- 默认值:proxy_cache_lock off;
- 上下文:http、server、location
- 这个多个请求,指的是同一请求的多个请求
1.2 proxy_cache_lock_timeout 指令
- 这个和1.1 一起使用,设置锁定的超时时间
- 语法:proxy_cache_lock_timeout time;
- 默认值:proxy_cache_lock_timeout 5s;
- 上下文:http、server、location
- 比如,这里有一个场景,比如说你第一个请求过来之后三个请求
- 之前设定了 proxy_cache_lock,其中有一个请求到达了应用服务器
- 假如说,由于某些原因,长时间有应用服务器故障不返回
- 五秒钟到了之后,后端的这两个请求依然会经过Nginx转发两个请求到应用服务器
- 这个是 这个 proxy_cache_lock_timeout 指令的作用
- 但是,这个还会有弊端,比如,现在是三个请求是5W个
- 如果把第一个请求分发给后端的应用服务器,但超时了
- 接下来可能有四万九千九百九十九的这样请求会直接由Nginx再次分发给应用服务器
- 在这样一种场景下,它依然会对应用服务造成很大的压力
- 所以说, 这个设定,在某些特殊场景下就失去了意义
- 为了避免这样一种情形的发生,引入下面的指令
1.3 proxy_cache_lock_age 指令
- 这个和1.2不同,当一个请求超时后,再发另一个请求尝试,接着继续尝试
- 语法:proxy_cache_lock_age time;
- 默认值:proxy_cache_lock_age 5s;
- 上下文:http、server、location
1.4 总结
-
以上三个参数就是通过合并原请求来规避了穿透Nginx这样一种情形的发生,从而避免缓存失效时,给我们的应用服务造成极大的一个压力
-
在具体的环境配置中,我们直接这样写就可以了
proxy_cache_path /opt/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=32g inactive=60m use_temp_path=off; upstream cache_server { server 192.168.184.20:1010; server 192.168.184.20:1011; } server { listen 80; server_name cache.baidu.com; # 注意,这里 if ($request_uri ~ \.(txt | text)$ ) { set $cookie_name "no cache"; } location / { proxy_cache cache zone; proxy_no_cache $cookie_name; proxy_cache_valid 200 5m; proxy_cache_key $scheme$proxy_host$request_uri add_header Nginx-Cache-Status "$upstream cache status"; proxy_pass http://cache server; # 关注如下 proxy_cache_lock on; proxy_cache_lock_timeout 5s proxy_cache_lock_age 5s } }
-
以上三个指令一起加上就可以完成合并原请求的实践
2 ) 启用陈旧缓存
- 缓存失效是降低上游服务压力的另外一种方法: 启用陈旧缓存
- 因为 Nginx 直接转发这样一些并发请求的话,可能会对上面的应用服务器造成一个致命的压力
- 如果这么多并发请求过来,有可能后面的这个应用服务器就直接给摊掉了
- 因此我们可以这样来做
- 虽然Nginx的缓存已经失效了, 但是比如说我第一个请求过来以后
- Nginx 会把第一个请求转发给后面的应用服务器
- 这个时候去请求更新本地缓存,就获取内容了
- 这个时候假如后面又有很多的请求过来了
- 跟第一个请求都请求的是同一个资源, 后面的这样一些请求
- 就没必要再转发给后面应用服务器了, 暂时等待第一个请求结果
- 虽然这样得到的不是实时的,是有些陈旧的
- 但是可以尽量避免并发请求全部透传到后端给上网应用服务造成的压力
- 对于某一些对数据一致性要求没那么高的场景来说的话是可以的
2.1 proxy_cache_use_stale 指令
- 语法:proxy_cache_use_stale error| timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | off …;
- 默认值:proxy_cache_use_stale off;
- 上下文:http、server、location
- 可选参数
可选参数 含义 error 与上游建立连接、发送请求、读取响应头出错时 timeout 与上游建立连接、发送请求、读取响应头超时时 invalid_header 无效头部时 updating 缓存过期,正在更新时 http_500 返回状态码500时 http_502 返回状态码502时 http_503 返回状态码503时 http_504 返回状态码504时 http_403 返回状态码403时 http_404 返回状态码404时 http_429 返回状态码429时 - 这个指令可以用来在特定的条件下去启用Nginx已有的陈旧的缓存
- 虽然可能这些缓存已经失效,但是对于某一些对数据一致性要求没有那么高的应用来说
- 能够返回一个结果, 远远比直接返回404,给客户的用户体验是要好的多
- 它后面有很多参数,每一个参数就是一种场景
- 比如说针对 error, timeout 或者是invalid_header, 以及 updating
- 后面还有很多不同的HTTP返回状态码的时候,都可启用陈旧缓存
- 这个指令和对上游应用服务进行容错指令 proxy_next_upstream 很相似
2.2 proxy_cache_background_update 指令
- 缓存更新时,在后台执行
- 语法:proxy_cache_background_update on | off;
- 设置为 off, 第一个请求过来,Nginx发现缓存失效
- 第一个请求被Nginx转发到上游服务器,等待获取结果并缓存
- 设置为 on, 第一个请求过来,会直接使用Nginx缓存
- 不管是否失效,Nginx自身会发起请求到应用服务器去更新资源
- 这种设置为 on 是用户体验比较好的
- 默认值:proxy_cache_background_update off;
- 上下文:http、server、location
2.3 配置示例
proxy_cache_path /opt/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=32g inactive=60m use_temp_path=off;
upstream cache_server {
server 192.168.184.20:1010;
server 192.168.184.20:1011;
}
server {
listen 80;
server_name cache.baidu.com;
# 注意,这里
if ($request_uri ~ \.(txt | text)$ ) {
set $cookie_name "no cache";
}
location / {
proxy_cache cache zone;
proxy_no_cache $cookie_name;
proxy_cache_valid 200 5m;
proxy_cache_key $scheme$proxy_host$request_uri
add_header Nginx-Cache-Status "$upstream cache status";
proxy_pass http://cache server;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s
proxy_cache_lock_age 5s
# 关注如下
proxy_cache_use_stale error timeout updating; # 这里配置三个
proxy_cache_background_update on;
}
}