Limiting Access to Proxied HTTP Resources
简介
NGINX 和 NGINX Plus 可以做如下控制:
- 每个key value 的连接数 (比如,每个IP地址)
- 每个key value 的请求速度 (在1s or 1m 内允许被处理的请求数量)
- 每个连接的下载速度
注意: IP地址可能是在NAT设备后面共享的,所以根据IP地址进行限制要慎用。
限制连接数
步骤如下:
- 使用
limit_conn_zone
指令定义key,设置shared memory zone (工作进程将使用该zone作为key values的共享计数器)。第一个参数,指定表达式作为key。第二个参数zone
,指定zone的名字和它的大小:
limit_conn_zone $binary_remote_addr zone=addr:10m;
- 使用
limit_conn
指令将限制应用到location {}
,server {}
, orhttp {}
上下文。指定shared memory zone的名字作为第一个参数,每个key允许的连接数作为第二个参数:
location /download/ {
limit_conn addr 1;
}
使用$binary_remote_addr
变量作为key,连接数的限制是基于IP地址的。
另外一种限制方式是基于服务器,使用 $server_name
变量:
http {
limit_conn_zone $server_name zone=servers:10m;
server {
limit_conn servers 1000;
}
}
限制请求速率
速度限制可以被用于防止DDoS攻击,或防止上游服务器被一段时间内大量的流量压垮。方法是基于 leaky bucket
算法:请求以各种速度到达桶,但是以修正的速度离开桶。
在使用速度限制前,需要配置“leaky bucket”的全局参数:
- key - 区别客户端的参数,通常是一个变量
- shared memory zone - zone的名字和大小,zone保存了这些keys的状态信息 (the “leaky bucket”)
- rate - 速度限制,请求数/秒 (
r/s
) or 请求数/分钟 (r/m
) (“leaky bucket draining”)
这些参数在 limit_req_zone
指令设置。该指令定义在http {}
层 - 这种方法允许对不同的上下文应用不同的区域和请求溢出参数:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}
如上配置,创建 shared memory zone 名称设置为one
,大小设置为10m。该 zone 保存了 $binary_remote_addr
变量所引用的客户端IP的状态。
注意:$remote_addr
也表现为客户端IP,但是$binary_remote_addr
表现为IP地址的二进制描述,更短。
shared memory zone 的空间调优可以参考如下数据:
$binary_remote_addr
的值是每个地址占用4 bytes ,在64位系统下状态信息的存储占用 128 bytes 。因此,16,000 IP地址的状态信息保存需要占用1m。
如果NGINX追加新条目的时候,存储空间满了,它将会溢出最老的条目。如果释放的空间让然不够容纳新记录,NGINX 会返回状态码 503 Service Unavailable
。状态码可以在 limit_req_status
指令重新定义。
zone 设置好后,你就可以在NGINX配置文件的server {}
, location {}
, or http {}
上下文应用该速度限制了:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one;
}
}
}
如上配置,NGINX 在处理/search/
的时候,处理速度不超过 1
请求/秒。这些请求的处理会被延迟,以使得总体速度不超过指定值。如果请求的数量超过了指定速度,NGINX 将延迟处理这些请求直到 桶 (shared memory zone one
) 满。 对于在桶满时候到达的请求,NGINX 返回503 Service Unavailable
错误 。
流控测试
在真实配置速度限制前,可以试试 dry run 模式,该模式不会真的限制请求处理速度。但是,溢出的请求仍然会在shared memory zone被统计,并被日志记录。dry run模式通过 limit_req_dry_run
指令开启:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one;
limit_req_dry_run on;
}
}
}
溢出的请求被记录时候,被标记以 “dry run” :
2019/09/03 10:28:45 [error] 142#142: *13246 limiting requests, dry run, excess: 1.000 by zone "one", client: 172.19.0.1, server: www.example.com, request: "GET / HTTP/1.0", host: "www.example.com:80"
处理溢出请求
请求被限制到 limit_req_zone
指令配置的速度。如果请求数量超过了指定速度,并且shared memory zone满了,NGINX 就会返回错误。然而流量就是突发性的,返回错误并不是好的处理方式。
这些溢出的请求可以被缓冲起来处理。limit_req
指令的burst
参数设置了可以在限定速度下被缓存处理的最大溢出请求数:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5;
}
}
}
如上配置,如果请求超过 1 请求/秒
,超过速度的请求将被放到 zone one。当 zone 满之后,溢出的请求将会被排队 (burst),队列的长度为 5 个请求
。队列内的请求会被延迟处理,这样总体速度不会超过限定速度。 超过burst限制的请求会被返回 503 错误。
如果流量突发不希望延迟处理,添加nodelay
参数:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5 nodelay;
}
}
}
如上配置,溢出请求不超过burst限制的部分会被立即处理,超过burst限制的部分会被返回 503。
延迟处理溢出请求
另一种处理溢出请求的方式是某个数值前的请求会被立即处理,当溢出请求即将被拒绝时应用流控。
这可以通过 delay
and burst
参数实现。delay
参数定义了一个点,在该点后的溢出请求会被延迟处理以匹配速度限制 :
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5 delay=3;
}
}
}
如上配置,前面 3 个请求 (delay) 被没有延迟的处理,接下来的 2 个请求 (burst - delay) 被延迟处理,这样总体速度不超过限制,更多的溢出请求会被拒绝因为总的burst已经溢出了,后续请求被延迟。
Synchronizing Contents of Many Shared Memory Zones
If you have a computer cluster with several NGINX instances and these instances use the limit_req
method, it is possible to sync the contents of their shared memory zones on conditions that:
- the
zone_sync
functionality is configured for each instance - shared memory zones set in the
limit_req_zone
directive for each instance have the same name - the
sync
parameter of thelimit_req_zone
directive is specified for each instance:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s sync;
}
See Runtime State Sharing in a Cluster for details.
限制带宽
限制每个连接的带宽,使用 limit_rate
指令:
location /download/ {
limit_rate 50k;
}
如上设置,一个客户端可以以单连接50k/s的速度下载内容。但是,客户端可以开启多个连接。那么,如果目的是限制下载速度,连接数也是需要被限制的。比如,每个IP可以开启一个连接:
location /download/ {
limit_conn addr 1;
limit_rate 50k;
}
要在客户端下载某个允许大小的数据后进行限速,使用 limit_rate_after
指令。客户端在到达某个限制前可以快速下载是合理需求 (比如,文件头 — 电影索引),然后对剩下的数据进行流控 (以确保在观影,而不是下载)。
limit_rate_after 500k;
limit_rate 20k;
如下范例展示了对连接数和带宽的联合限制。每个客户端的最大连接数限制为5 ,适合大部分场景,因为现代浏览器通常同时打开3个连接。同时,downloads
的下载被限制为1个连接:
http {
limit_conn_zone $binary_remote_address zone=addr:10m
server {
root /www/data;
limit_conn addr 5;
location / {
}
location /download/ {
limit_conn addr 1;
limit_rate_after 1m;
limit_rate 50k;
}
}
}
Dynamic Bandwidth Control
limit_rate
的值可以设备为变量 - 这就允许可以动态的调整带宽,比如,允许现代浏览器的带宽更高:
map $ssl_protocol $response_rate {
"TLSv1.1" 10k;
"TLSv1.2" 100k;
"TLSv1.3" 1000k;
}
server {
listen 443 ssl;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
location / {
limit_rate $response_rate; # Limit bandwidth based on TLS version
limit_rate_after 512; # Apply limit after headers have been sent
proxy_pass http://my_backend;
}
}