Nginx内容缓存
从你代理的web和应用服务器缓存静态和动态内容,来加速到客户端的传送,减少服务器的负载。
题外话:始终记得,缓存的是被代理服务器返回的响应,是响应!
概览
当缓存开启时,Nginx在磁盘中保存响应的缓存并且使用它们来响应客户端,无需每次都为相同的内容而代理。
开启响应缓存
想要开启缓存,在顶级的http {}
上下文中包含proxy_cache_path
指令,第一个必要参数是缓存内容的本地文件系统路径,必要参数keys_zone
定义了缓存的名字和它用于存储缓存项目的元数据的共享内存区域(zone)的大小:
http {
# ...
proxy_cache_path /data/nginx/cache keys_zone=mycache:10m;
}
然后,在你想要缓存服务器响应的上下文(协议类型、虚拟服务器或者location)中包含proxy_cache
指令,指定由proxy_cache_path
指令的keys_zone
参数定义的区域名(在这个例子中是mycache
):
http {
# ...
proxy_cache_path /data/nginx/cache keys_zone=mycache:10m;
server {
proxy_cache mycache;
location / {
proxy_pass http://localhost:8000;
}
}
}
注意,keys_zone
参数中指定的大小并不能限制被缓存的响应数据的总量。缓存响应本身存储在文件系统中的特定文件中的元数据副本中。想要限制缓存的响应数据的总量,在proxy_cache_path
指令中包含max_size
参数。(但是注意,缓存数据的总量可能会临时性的超过这个限制,下面部分会讲述它)
关于缓存不生效的问题
照着官网的例子,我也做了一下,但是我的缓存怎么都不生效,以下是我用的环境:
- docker nginx:1.23.0
- nodejs + express搭建的服务器
下面是我的nginx.conf
user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
# 指定缓存路径以及共享内存区名字及大小
proxy_cache_path /cache keys_zone=mycache:10m;
# 三台上游服务器
upstream backend {
server bk1:3000 weight=10;
server bk2:3001;
server bk3:3002;
}
# 反向代理,负载均衡到三台上游服务器上
server {
# 使用缓存 mycache
proxy_cache mycache;
listen 8080;
location / {
proxy_pass http://backend;
}
}
}
随后,我对反向代理服务器进行访问,而请求每次都实际到达了服务器中:
后来,我在反向代理的server上下文中添加了如下指令:
server {
proxy_cache mycache;
+ proxy_cache_valid any 48h;
listen 8080;
location / {
proxy_pass http://backend;
}
}
也就是设置对于任何状态码的响应,缓存的有效时间都为48h,这下第一次访问后再也没有实际走到服务器中:
而且,缓存目录下也有了文件:
以下是我通过搜索得到的一些其它缓存无效的原因:
- nginx的工作用户没有缓存目录的权限
- 被代理的服务器返回的响应中明确指定不让缓存(
Caching-Control
头指定的),或者具有Set-Cookie
头
对于第二点,一般这证明被访问的页面是一个动态页面,确实不需要缓存。如果确实有缓存的必要,那么可以通过proxy_ignore_headers
来忽略被代理服务器响应头中的指定字段。
具体可以参考:A Guide to Caching with NGINX and NGINX Plus
涉及缓存的Nginx进程
有两个额外的Nginx进程涉及到缓存:
- cache manger周期性的被激活,以检查缓存状态。如果缓存大小达到
proxy_cache_path
命令的max_size
参数的限制,缓存管理器将移除最近最少访问的缓存数据。就像前面提到的,缓存数据的量在缓存管理器两次激活的时间中间可能临时的超出限制 - cache loader只在nginx启动时运行一次。它加载之前缓存的元数据到共享内存区域中。一次加载整个缓存可能会消耗很多资源,这会让Nginx的性能在启动后的前几分钟下降。为了避免,你可以配置迭代式(iterative)加载缓存,可以使用
proxy_cache_path
指令的下列参数来配置:loader_threshold
- 迭代持续时间,毫秒(默认是200)loader_files
- 一次迭代最大加载的缓存项数量(默认是100)loader_sleeps
- 两次迭代间的延时,毫秒(默认是50)
在下面的例子中,迭代持续300毫秒或者直到200个缓存项被加载了:
proxy_cache_path /data/nginx/cache keys_zone=mycache:10m loader_threshold=300 loader_files=200;
缓存指定请求
默认情况下,Nginx缓存所有HTTP GET和HEAD产生的请求的响应,这会在响应在第一次从被代理服务器中到达时发生。Nginx使用请求字符串来作为请求的键(标识符),如果一个请求与一个被缓存的响应具有相同的键,Nginx发送缓存的响应到客户端。你可以在http {}
、server {}
或location {}
上下文中包含多个指令来控制哪个响应将被缓存。
为了修改在计算请求键时使用的请求特征,可以使用proxy_cache_key
指令:
proxy_cache_key "$host$request_uri$cookie_user";
可以使用proxy_cache_min_uses
指令定义具有相同键的请求必须创建几次,它的响应才会被缓存:
proxy_cache_min_uses 5;
为了缓存一个除了使用GET和HEAD方法之外的请求,可以使用proxy_cache_methods
指令来指定:
proxy_cache_methods GET HEAD POST;
限制或者关闭缓存
默认情况下,响应无限期的缓存在缓存中。它们只有当缓存到达的配置的最大大小时它们才会被移除,而且是按照它们自上次请求以来的时间长度进行删除。通过在http {}
、server {}
或者location {}
上下文中包含指令,你可以设置缓存响应被认为有效的时间,甚至设置它们是否被使用。
可以使用proxy_cache_vaild
指令来设置一个具有特定状态码的响应将在多久内有效:
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
在这个示例中,具有200或302响应码的响应在10分钟内被认为是有效的,而具有404状态码的响应在1分钟之内被认为是有效的。如果想要对任何状态码的响应定义有效性,在第一个参数中使用any
:
proxy_cache_valid any 5m;
使用proxy_cache_bypass
指令可以定义Nginx不发送缓存响应到客户端的条件。每一个参数定义了一个条件,包含一些变量。如果至少一个参数不是空的并且不为“0”,Nginx Plus不使用缓存中的响应,而是直接将请求立即转发到后端服务器上。
proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
可以使用proxy_no_cache
指令来定义Nginx完全不缓存响应的条件,使用与proxy_cache_bypass
指令相同的方式来定义:
proxy_no_cache $http_pragma $http_authorization;
从缓存中清除内容
Nginx可以从缓存中清除过时缓存文件,这对于移除过时的缓存内容以阻止同时提供老和新版本的web页面是很有必要的,当收到一个具有自定义HTTP Header的或HTTP PURGE
请求方法的特殊“purge”请求时,缓存会被清除。
配置缓存清除
让我们设置一个配置来标识使用HTTP PURGE
方法的请求并删除匹配的URL。
- 在
http {}
上下文中,创建一个新的变量,比如$purge_method
,这依赖于$request_method
变量。http { # ... map $request_method $purge_method { PURGE 1; default 0; } }
- 在缓存被配置的
location {}
块,使用proxy_cache_purge
来为缓存清理请求指定一个条件。在我们的例子中,它是在前面的步骤中被$purge_method
所配置的:server { listen 80; server_name www.example.com; location / { proxy_pass https://localhost:8002; proxy_cache mycache; proxy_cache_purge $purge_method; } }
map
指令可以看作是一个switch-and-set
指令,它是一个选择结构,它首先匹配HTTP请求的$request_method
,若它是PURGE
,就设置$purge_method = 1
,默认情况下,它是0
。所以,后面能够通过proxy_cache_purge $purge_method
来按情况清除缓存。
发送Purge命令
当proxy_cache_purge
指令被配置后,你需要发送一个特定的缓存清理请求来清理缓存。你可以使用如下工具来提交purge请求,比如下面例子中的curl
命令:
$ curl -X PURGE -D – "https://www.example.com/*"
HTTP/1.1 204 No Content
Server: nginx/1.15.0
Date: Sat, 19 May 2018 16:33:04 GMT
Connection: keep-alive
在这个示例中,具有公共URL部分的资源将被清除。然而,这样缓存项并未完全从缓存中移除:它们仍然保存在磁盘上,直到它们失活(通过proxy_cache_path
的inactive
参数指定)或被缓存清除器(通过proxy_cache_path
的purger
参数指定)或客户端尝试访问。
上一段我也翻译不太好,因为没有实际的应用场景让我尝试。但我的理解就是,为了性能考虑,缓存的清除是惰性的,因为短时间内清除大量缓存是耗时的IO操作,所以把它们均摊到每次请求上或由一个类似清理器的东西异步慢慢删除。很多高性能的设计中都考虑到了这一点。
限制Purge命令的访问
我们建议你限制被允许发送缓存清除请求的IP地址:
geo $purge_allowed {
default 0; # deny from other
10.0.0.1 1; # allow from 10.0.0.1 address
192.168.0.0/24 1; # allow from 192.168.0.0/24
}
map $request_method $purge_method {
PURGE $purge_allowed;
default 0;
}
这种变量设置方式头一次见,真有意思。
在上面的例子中,Nginx检查是否PURGE
方法被应用在这个请求中,如果是的话,分析客户端的IP地址,如果IP地址在白名单中,$purge_method
就会被设置成$purge_allowed: 1
,允许清除缓存,否则就是0
,禁止。
从缓存中完整移除文件
想要完全的移除匹配一个星号的缓存文件,激活一个特定的cache purger
进程,这个进程长期的迭代,直到匹配的缓存项都被清除。在http {}
上下文的proxy_cache_path
指令中使用purger
参数可以做到:
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:10m purger=on;
缓存清除配置示例
http {
# ...
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:10m purger=on;
map $request_method $purge_method {
PURGE 1;
default 0;
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass https://localhost:8002;
proxy_cache mycache;
proxy_cache_purge $purge_method;
}
}
geo $purge_allowed {
default 0;
10.0.0.1 1;
192.168.0.0/24 1;
}
map $request_method $purge_method {
PURGE $purge_allowed;
default 0;
}
}
字节范围缓存(Byte-Range Caching)
初始的缓存填充操作有时可能会耗费大量的时间,尤其是对于一个很大的文件来说。举个例子,当一个视频文件开始下载以满足对该文件的一部分的初始请求时,后续的请求必须等待整个文件被下载下来并放到缓存中。
Nginx允许使用Cache Slice模块来缓存这样的范围请求并且逐步的填充缓存,即把文件分割成小的片段。每一个范围请求选择一个覆盖被请求范围的特定的切片,如果这个切片还没有被缓存,就将它放到缓存中。对于这个切片的所有其它请求都可以从缓存中拿到这个数据。
要开启byte-range caching:
- 确保Nginx与[Cache Slice]模块一同被编译
- 使用
slice
指令指定切片大小location / { slice 1m; }
选择一个切片大小可以让切片被快速下载。如果size太小了,内存可能会被过度消耗并且大量的文件描述符在处理该请求时被打开,而一个极大的大小又可能导致延迟。
- 使用\(slice_range\)变量到cahce key中:
proxy_cache_key $uri$is_args$args$slice_range;
- 开启206状态码的响应缓存
proxy_cache_valid 200 206 1h;
- 通过设置
Range
头中的$slice_range
变量来开启传递部分请求到被代理服务器proxy_set_header Range $slice_range;
下面是完整的配置:
location / {
slice 1m;
proxy_cache cache;
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
proxy_cache_valid 200 206 1h;
proxy_pass http://localhost:8000;
}
整合配置示例
http {
# ...
proxy_cache_path /data/nginx/cache keys_zone=mycache:10m loader_threshold=300
loader_files=200 max_size=200m;
server {
listen 8080;
proxy_cache mycache;
location / {
proxy_pass http://backend1;
}
location /some/path {
proxy_pass http://backend2;
proxy_cache_valid any 1m;
proxy_cache_min_uses 3;
proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
}
}
}
在上面的例子中,两个locations以不同的方式使用同一个缓存。
因为backend1
的响应很少改变,所以不包括缓存控制指令。请求第一次发起,它的响应就被缓存,并且永久有效。
相反,backend2
的请求响应频繁改变,所以它们被认为在1分钟之内有效,并且直到同一个请求被发起3次才缓存。而且,如果一个请求匹配proxy_cache_bypass
指令定义的条件,Nginx立即将请求传递到backend2
,而不寻找缓存中对应的响应。