Skip to main content

16.限制内容访问速率

根据客户端 IP 地址或其他变量限制连接、请求速率或带宽,从而保护upstream Web 和应用程序服务器。本文介绍如何设置连接的最大请求数或从服务器下载内容的最大速率。

使用NGINX,可以限制:

  • 每个密钥值的连接数(例如,每个 IP 地址)
  • 每个键值的请求速率(允许在一秒或一分钟内处理的请求数)
  • 连接的下载速度

请注意,IP 地址可以在 NAT 设备后面共享,因此应明智地使用 IP 地址限制。

限制连接数(Limiting the Number of Connections)

要限制连接数:

  1. 使用 limit_conn_zone 指令定义键并设置共享内存区域的参数(工作进程将使用此区域共享键值的计数器)。作为第一个参数,指定计算为键的表达式。在第二个参数 zone中,指定区域的名称及其大小:

    limit_conn_zone $binary_remote_addr zone=addr:10m;
  2. 使用 limit_conn 指令在 location {}server {}http {}上下文中应用限制。将共享内存区域的名称指定为第一个参数,将每个密钥允许的连接数指定为第二个参数:

    location /download/ {
    limit_conn addr 1;
    }

    连接数因 IP 地址而异,因为 $binary_remote_addr 变量用作密钥。

    限制给定服务器连接数的另一种方法是使用 $server_name 变量:

    http {
    limit_conn_zone $server_name zone=servers:10m;

    server {
    limit_conn servers 1000;
    }
    }

限制请求速率(Limiting the Request Rate)

速率限制可用于防止 DDoS 攻击,或防止上游服务器同时被过多请求淹没。该方法基于泄漏存储桶算法:请求以不同的速率到达存储桶,并以固定速率离开 存储桶

在使用限速之前,需要配置“泄漏桶”的全局参数:

  • key - 用于区分一个客户端与另一个客户端的参数,通常是一个变量
  • 共享内存区域(shared memory zone) - 保存这些密钥状态的区域的名称和大小(“泄漏存储桶”)
  • 速率(rate) - 在每秒请求数 (r/s) 或每分钟请求数 (r/m) 中指定的请求速率限制(“漏桶排空”)。每分钟请求数用于指定每秒小于一个请求的速率。

这些参数使用 limit_req_zone 指令设置。该指令是在 http {}级别上定义的 - 这种方法允许将不同的区域和请求溢出参数应用于不同的上下文:

http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}

使用此配置,将创建大小为 10 MB 的共享内存区域 one。 该区域保留使用 $binary_remote_addr 变量设置的客户端 IP 地址的状态。请注意,与同时保存客户端 IP 地址的 $remote_addr 相比,$binary_remote_addr 保存的 IP 地址的二进制表示形式更短。

可以使用以下数据计算共享内存区域的最佳大小: 对于 IPv4 地址,$binary_remote_addr 值的大小为 4 个字节, 存储状态在 128 位平台上占用 64 个字节。因此,大约 16,000 个 IP 地址的状态信息占用 1 MB 的区域。

如果NGINX需要添加新条目时存储空间已耗尽,则会删除最旧的条目。如果释放的空间仍然不足以容纳新记录,NGINX 将返回状态代码 503 Service Unavailable。可以使用 limit_req_status 指令重新定义状态代码。

设置区域后,您可以使用限制 NGINX 配置中任何位置的请求,并为 server,{}location {}http {}上下文指定limit_req

http {
#...

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

server {
#...

location /search/ {
limit_req zone=one;
}
}
}

使用此配置,NGINX在 /search/该位置内每秒处理的请求不会超过 1。这些请求的处理延迟,总速率不大于指定。如果请求数超过指定的速率,NGINX将延迟处理此类请求,直到“存储桶”(共享内存区域 one)已满。对于到达整个存储桶的请求,NGINX 将响应错误 503 Service Unavailable(如果未使用 limit_req_status 重新定义)。

测试请求速率限制(Testing the Request Rate Limit)

在配置实际速率限制之前,您可以尝试不限制请求处理速率的“试运行(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;
}
}
}

每个超过定义的速率限制的请求都将记录“试运行”标记:

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 指令中定义的速率。如果请求数超过指定速率并且共享内存区域已满,NGINX 将响应错误。由于流量往往是突发的,因此在流量突发期间返回错误以响应客户端请求并不是最佳情况。

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请求数,超出速率的请求将被放入区域 one 中。当区域已满时,过多的请求将排队(burst),此队列的大小为 5请求。队列中的请求处理延迟的方式是,总体速率不大于指定的速率。超过突发限制的请求将被拒绝并显示错误 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;
}
}
}

使用此配置,无论指定 rate如何,都会立即为 burst限制内的过多请求提供服务,超过突发限制的请求将被拒绝并返回 503 错误。

延迟过多的请求

处理过多请求的另一种方法是毫不拖延地处理其中一些请求,然后应用速率限制,直到过多的请求被拒绝为止。

这可以通过 delayburst参数来实现。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 ) 以不大于指定速率的方式延迟,进一步的过多请求将被拒绝,因为已超过总突发大小,后续请求将被延迟。

同步多个共享内存区域的内容(Limiting the Bandwidth)

如果您有一个包含多个 NGINX 实例的计算机集群,并且这些实例使用 limit_req该方法,则可以在以下条件下同步其共享内存区域的内容:

http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s sync;
}

有关详细信息,请参阅集群中的运行时状态共享

限制带宽(Limiting the Bandwidth)

要限制每个连接的带宽,请使用 limit_rate 指令:

location /download/ {
limit_rate 50k;
}

使用此设置,客户端将能够通过单个连接以每秒 50k字节的最大速度下载内容。但是,客户端可以打开多个连接。因此,如果目标是防止下载速度大于指定值,则还应限制连接数。例如,每个 IP 地址一个连接(如果使用上面指定的共享内存区域):

location /download/ {
limit_conn addr 1;
limit_rate 50k;
}

若要仅在客户端下载一定数量的数据后施加限制,请使用 limit_rate_after 指令。允许客户端快速下载一定数量的数据(例如,文件头 - 电影索引)并限制下载其余数据的速率(让用户观看电影,而不是下载)可能是合理的。

limit_rate_after 500k;
limit_rate 20k;

以下示例显示了用于限制连接数和带宽的组合配置。允许的最大连接数 5设置为每个客户端地址的连接数,这适合最常见的情况,因为现代浏览器通常一次最多打开 3 个连接。同时,提供下载服务的位置只允许一个连接:

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;
}
}
}

动态带宽控制

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;
}
}