概述
限流(Rate Limiting)是一种有效的系统保护机制,通过控制系统的输入和输出流量来缓解潜在的压力和风险。在网站运行于公网环境时,面对用户正常访问、网络爬虫、恶意攻击或突发大流量等情况,系统可能会面临过载的风险,从而导致响应延迟甚至系统崩溃的问题。
针对这种情况,限流技术能够有效地管理并发请求,保障系统的稳定运行。通过对一部分请求进行限制,例如限制同一IP地址的频繁请求,系统可以合理地分配资源,确保服务器能够正确响应其他请求。
Nginx提供了两种主要的限流方式:一种是限制请求速率,另一种是限制连接数量。此外,Nginx还支持对下载和上传速度进行限制,进一步加强了系统的稳定性和安全性。
请求速率
nginx 的 ngx_http_limit_req_module
模块实现了请求处理速率的限制功能,采用了漏桶算法(leaky bucket algorithm)。
这种算法可以被形象地描述为一个桶,水从上面源源不断地进入,而底部以固定的速率匀速流出。如果桶里的水超过了一定的限制,那么新来的水就会被拒绝或者丢弃。
在网络请求处理中,桶可以看作是一个队列,而水则代表着来自客户端的请求。当请求到达时,如果桶还有足够的容量,则请求将被接受并放入队列中等待处理。但是,如果队列已满,那么新的请求就会被暂时拒绝或者丢弃,直到队列中有足够的空间接受新的请求。这种方式可以有效地控制服务器的请求处理速率,防止过多的请求导致服务器过载。
limit_req_zone
nginx 中有两个主要的指令可以用来配置限流:limit_req_zone
和 limit_req
。
来看个Demo
代码语言:javascript复制limit_req_zone $binary_remote_addr zone=artisan:10m rate=2r/s;
server {
location / {
limit_req zone=artisan;
}
}
limit_req_zone 用于设置限流和共享内存区域的参数,格式为:limit_req_zone key zone rate
limit_req_zone
是nginx中的一个指令,用于配置请求限流的相关参数,主要包括限流对象、共享内存区域和最大访问速率。
下面是对limit_req_zone
指令各部分的介绍:
- key(限流对象):这个参数定义了限流的对象,即被限制的请求的来源。在示例中,使用了 binary_remote_addr,表示根据客户端IP地址来进行限流。由于使用了binary_remote_addr而不是remote_addr,是为了减少内存消耗。因为remote_addr的大小在IPv4情况下是7到15个字节不等,而
- zone(共享内存区域):这个参数定义了一个共享内存区域来存储访问信息,包括每个IP地址的状态和访问受限请求URL的频率等。在示例中,
artisan:10m
表示一个大小为10M,名字为artisan
的共享内存区域。nginx会在这个区域中记录请求的访问信息,以便进行限流。当需要存储新的记录时,nginx会根据一定的策略清理旧的记录,以释放空间。
1M 能存储16000个 IP 地址的访问信息,artisan大概可以存储约160000个地址。nginx 创建新记录的时候,会移除前60秒内没有被使用的记录,如果释放的空间还是存储不了新的记录,会返回503的状态码。默认返回503,如果想修改返回值,可以设置
limit_req_status
- rate(最大访问速率):这个参数设置了最大的访问速率。在示例中,
rate=2r/s
表示每秒最多处理2个请求。实际上,nginx以毫秒为粒度追踪请求,所以这个设置实际上是每500毫秒处理1个请求。如果请求到达的速率超过了这个限制,那么多余的请求可能会被拒绝或延迟处理。
上面的例子只简单指定了 zone=artisan,表示使用 artisan这个区域的配置。我们可以理解为这个桶目前没有任何储存水滴的能力,到达的所有不能立即漏出的请求都会被拒绝。如果我1秒内发送了10次请求,其中前500毫秒1次,后500毫秒9次,那么只有前500毫秒的请求和后500毫秒的第一次请求会响应,其余请求都会被拒绝。
limit_req
limit_req_zone
指令用于设置限流的参数,但它本身并不会应用限流规则。要使限流规则生效,还需要配合使用limit_req
指令,将limit_req_zone
定义的限流区域应用到具体的请求处理中。
limit_req
是nginx中用于应用请求限流规则的指令,其格式为:
limit_req zone=name [burst=number] [nodelay];
下面是各部分参数的说明:
- zone:这个参数指定了之前使用
limit_req_zone
指令定义的限流区域的名称。在配置中,使用zone=name
将相应的限流区域应用到当前的limit_req
规则中。 - burst:这是一个可选参数,用于设置允许的最大突发请求数。默认情况下,当请求超过设置的速率时,nginx会将多余的请求放入队列中等待处理,直到队列满或突发请求数达到上限。设置了
burst
参数后,nginx会允许一定数量的请求超过限制,直到达到设定的突发请求数。例如,burst=10
表示允许最多同时处理10个超过限制的请求。 - nodelay:这也是一个可选参数,用于指定是否启用无延迟模式。默认情况下,当请求超过限制时,nginx会将请求放入队列中等待处理,直到队列有空间为止。启用了
nodelay
参数后,nginx会立即拒绝超过限制的请求,而不会将其放入队列中等待处理。这种模式可以减少延迟,但可能会导致某些请求被直接拒绝。
通过使用limit_req
指令,可以将之前定义的限流区域应用到具体的请求处理中,从而实现对请求的限流控制。
burst突发流量
确实,对于有突发流量的情况,仅仅依靠固定的速率来限制请求可能会导致部分请求被直接拒绝,而不能充分利用服务器资源。为了应对突发流量,可以使用 burst
参数来允许一定数量的请求超过设定的限制,这样可以缓解突发流量对系统的影响,同时仍然保持对请求的限流控制。
例如,我们可以调整 limit_req
指令的格式,增加 burst
参数来实现这个目的:
limit_req zone=name burst=20;
在这个例子中,除了使用了之前定义的限流区域 name
,还设置了 burst=20
,表示允许同时处理的最大突发请求数为20个。这样,当突发流量到达时,nginx会先允许一定数量的请求超过设定的速率,直到达到设定的突发请求数上限,然后再按照设定的速率进行处理。
通过调整 burst
参数,可以根据实际情况灵活地配置请求的限流策略,既可以应对突发流量,又可以保护服务器资源不被过度消耗。
继续刚才的例子:
代码语言:javascript复制limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
server {
location / {
limit_req zone=test burst=5;
}
}
burst 表示在超过设定的访问速率后能额外处理的请求数。当 rate=2r/s 时,表示每500ms 可以处理一个请求。
burst=5时,如果同时有10个请求到达,nginx 会处理第1个请求,剩余9个请求中,会有5个被放入队列,剩余的4个请求会直接被拒绝。
然后每隔500ms从队列中获取一个请求进行处理,此时如果后面继续有请求进来,如果队列中的请求数目超过了5,会被拒绝,不足5的时候会添加到队列中进行等待。我们可以理解为现在的桶可以存5滴水:
nodelay不延迟
配置 burst 之后,虽然同时到达的请求不会全部被拒绝,但是仍需要等待500ms 一次的处理时间,放入桶中的第5个请求需要等待500ms * 4的时间才能被处理,更长的等待时间意味着用户的流失,在许多场景下,这个等待时间是不可接受的。此时我们需要增加 nodelay 参数,和 burst 配合使用。
代码语言:javascript复制limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
server {
location / {
limit_req zone=test burst=5 nodelay;
}
}
nodelay 表示不延迟。设置 nodelay 后,第一个到达的请求和队列中的请求会立即进行处理,不会出现等待的请求。
需要注意的是,虽然队列中的5个请求立即被处理了,但是队列中的位置依旧是按照500ms 的速度依次被释放的。后面的4个请求依旧是被拒绝的,长期来看并不会提高吞吐量的上限,长期吞吐量的上限是由设置的 rate 决定的。
因此,需要根据实际需求和系统负载来权衡设置这些参数,以达到最佳的性能和用户体验。
通过ngx_http_geo_module和ngx_http_map_module设置白名单
通过使用nginx的ngx_http_geo_module
和ngx_http_map_module
模块,可以配置一个白名单,即允许某些IP地址不受限流的影响,从而取消对这些IP地址的请求限流设置。
下面是一个简单的示例配置:
代码语言:javascript复制geo $limit {
default 1; # 默认情况下,所有IP都受限流影响
10.0.0.1/32 0; # 白名单中的IP地址不受限流影响
192.168.1.0/24 0;
}
map $limit $limit_key {
1 "";
0 $binary_remote_addr;
}
limit_req_zone $limit_key zone=artisan:10m rate=2r/s;
server {
location / {
limit_req zone=artisan burst=5 nodelay;
# 其他配置...
}
}
在这个配置中:
-
geo
块用于定义一个名为$limit
的变量,该变量根据请求的IP地址来决定是否受限流影响。默认情况下,所有IP地址都受限流影响,但在白名单中的IP地址不受影响。 - map 块将 limit 变量映射为 limit_key 变量。当 limit 的值为1时,表示不在白名单中,将使用 binary_remote_addr(客户端IP地址)作为限流区域的键值;当
-
limit_req_zone
指令根据$limit_key
变量来定义限流区域。对于白名单中的IP地址,将不受限流影响,因此不会被放入限流区域。
通过这样的配置,可以实现对白名单中的IP地址取消限流设置,从而在需要时对特定IP进行压测或其他操作而不受限制。
多个limit_req规则
在这个配置示例中,定义了两个规则 mylimit
和 myLimit2
,分别针对不同的限流策略。通过使用 geo
和 map
模块,可以根据请求的来源决定是否受限流的影响,从而实现白名单功能。
具体的配置如下:
代码语言:javascript复制geo $limit {
default 1; # 默认情况下,所有IP地址受限流影响
10.0.0.0/8 0; # 白名单中的IP地址不受限流影响
192.168.0.0/24 0;
}
map $limit $limit_key {
0 ""; # 在白名单中的IP地址不受限流影响
1 $binary_remote_addr; # 不在白名单中的IP地址使用二进制的远程地址作为限流区域的键值
}
limit_req_zone $limit_key zone=mylimit:10m rate=2r/s; # 定义限流区域 mylimit,速率为2r/s
limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=10r/s; # 定义限流区域 myLimit2,速率为10r/s
server {
location ~* .(html)$ {
limit_req zone=mylimit burst=5 nodelay; # 对于不在白名单中的IP,使用 mylimit 规则,限制为 2r/s
limit_req zone=myLimit2 burst=5 nodelay; # 对于所有IP,无论是否在白名单中,使用 myLimit2 规则,限制为 10r/s
}
}
在这个配置中,对于白名单中的IP地址,因为其匹配到了 myLimit2
规则,所以被限制为10r/s。而对于不在白名单中的IP地址,需要同时匹配 mylimit
和 myLimit2
两个规则,而两者中最严格的条件是 mylimit
的2r/s,因此会起作用。
这种配置方式能够很灵活地根据请求的来源对请求进行不同程度的限流控制,满足不同场景下的需求。
限制连接数
nginx的ngx_http_limit_conn_module
模块提供了限制连接数的功能,其中包含两个关键指令:limit_conn_zone
和limit_conn
。这两个指令的格式分别为limit_conn_zone key zone。
limit_conn_zone
- limit_conn_zone:这个指令用于设置连接数的限制参数和共享内存区域。其中,key表示限制对象,zone表示共享内存区域。在给定的内存区域中,nginx会存储关于连接数的相关信息,以便进行限制。
limit_conn
- limit_conn:这个指令用于在特定的位置(例如server或location)设置连接数的限制规则。通过指定限流区域和连接数的上限,可以控制特定对象(如IP地址或虚拟主机)同时持有或处理的连接数量。
参考配置
代码语言:javascript复制limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
location ~* .(html)$ {
limit_conn perip 10;
limit_conn perserver 100;
}
}
- limit_conn_zone binary_remote_addr zone=perip:10m:这条指令设置了以binary_remote_addr为限制对象,即限制单个IP同时最多能持有10个连接,并将这个信息存储在名为perip的10M共享内存区域中。
- limit_conn_zone server_name zone=perserver:10m:这条指令设置了以server_name为限制对象,即限制虚拟主机(server)同时能处理的并发连接总数为100,并将这个信息存储在名为perserver的10M共享内存区域中。
在配置的server
块中的location
段,通过limit_conn
指令将特定的连接数限制规则应用到相应的请求处理中。通过这样的配置,可以有效地控制特定对象的连接数,防止过多的连接导致服务器负载过高或性能下降。
上传/下载速率限制
limit_rate主要用于限制用户和服务器之间传输的字节数,通常用于下载/上传限速等场景。这个功能并不是一个单独的模块,而是在ngx_http_core_module
模块中实现的。相关指令包括limit_rate和limit_rate_after
,用于控制限速的策略。
limit_rate
limit_rate 指令用于设置连接上的限速速率。通过在配置文件中设置limit_rate指令,并指定希望限制的速率,可以控制连接上的传输速度。这个速率可以是固定的值,也可以是根据需要动态调整的值。
代码语言:javascript复制server {
location / {
limit_rate 4k;
}
}
- 默认的单位是bytes/s,也就是每秒传输的字节数Bytes而不是比特数bits
- rate可以设置为变量,从而可以实现动态限速
- 限速指令的生效范围是根据每个连接确定的,例如上面限定每个连接的速率为4k,也就是当客户端发起两个连接的时候,速率就可以变为8k
limit_rate_after
limit_rate_after 指令用于设置在响应的开始部分不受限速限制的字节数。通常用于避免限速影响响应头部或其他重要信息的传输。设置了limit_rate_after后,在达到指定的字节数后才会开始应用限速策略。
代码语言:javascript复制server {
location / {
limit_rate_after 500k;
limit_rate 4k;
}
}
limit_rate_after
指令允许在传输了一部分数据之后再进行限速,这样可以应对一些特定的场景,比如分段下载限速或流媒体视频网站的带宽控制。
举例来说,在一个配置中,如果设置了limit_rate_after 500k
,这意味着在传输了500KB的数据之后才会启用限速策略。这样可以确保在开始传输时不会受到限速的影响,保证了一开始的传输速度;而在传输了一定量的数据之后,再根据需要进行限速,以控制带宽消耗。
这种配置在分段下载限速或流媒体视频网站中特别有用。在流媒体视频网站中,为了保证用户体验,通常不会对第一个画面进行限速,以便尽快加载出来。等用户开始观看视频后,再将带宽限制在合理的范围内,以降低因客户端网速过快导致提前加载过多内容带来的额外成本。
通过合理配置limit_rate_after
指令,可以实现更灵活的限速策略,同时提供更好的用户体验和网络资源利用效率。
总之,通过合理配置这两个指令,可以实现对用户和服务器之间传输字节数的限速控制,从而保护服务器资源,优化用户体验。
proxy_limit_rate
proxy_limit_rate是Nginx中用于控制向后端代理服务器发送请求的速率的指令。当Nginx作为反向代理时,它可能会向后端服务器发送大量的请求,如果不加以限制,可能会导致后端服务器的过载或不必要的资源消耗。
通过使用proxy_limit_rate指令,可以限制向后端服务器发送请求的速率,以防止对后端服务器造成过大的压力。这可以帮助你平衡反向代理服务器和后端服务器之间的负载,确保系统的稳定性和可靠性。
proxy_limit_rate的语法如下:
代码语言:javascript复制proxy_limit_rate rate;
其中,rate是一个表示速率的数字,可以是带单位的数字,如1k、1m,表示每秒发送的数据量。
例如,如果你想限制向后端服务器发送请求的速率为每秒100KB,可以这样配置:
代码语言:javascript复制proxy_limit_rate 100k;
需要注意的是,proxy_limit_rate指令仅适用于HTTP和HTTPS反向代理情况,对于其他协议(如FastCGI等),可能需要使用不同的指令来实现类似的功能。
代码语言:javascript复制proxy_limit_rate的基本原理和用法与limit_rate几乎一样,唯一不同的是proxy_limit_rate是限制nginx和后端upstream服务器之间的连接速率而limit_rate限制的是nginx和客户端之间的连接速率。需要注意的是proxy_limit_rate需要开启了proxy_buffering这个指令才会生效
#语法:
Syntax: proxy_limit_rate rate;
Default: proxy_limit_rate 0;
Context: http, server, location
This directive appeared in version 1.7.7.
另外,proxy_limit_rate仅限制了向后端服务器发送请求的速率,并不影响从后端服务器接收响应的速率。如果需要限制接收响应的速率,可以结合使用proxy_set_header和limit_rate_after等指令来实现。
动态限速
Nginx的limit_rate
指令可以与变量和map
指令等结合使用,从而实现动态限速功能。这种组合可以根据请求的特定属性或条件来动态地调整响应的发送速率,使得限速更加灵活和智能。
举个例子,可以使用map
指令定义一个映射,根据请求的特定属性(如客户端IP、请求路径等)将请求映射到不同的限速值。然后,将这个映射结果作为变量传递给limit_rate
指令,从而实现动态限速。
以下是一个简单的示例,假设你想根据请求的路径来动态限速:
代码语言:javascript复制map $request_uri $limit_rate {
default 1m; # 默认限速为1MB/s
/images/ 500k; # 对/images/路径下的请求限速为500KB/s
/videos/ 2m; # 对/videos/路径下的请求限速为2MB/s
}
server {
...
location / {
limit_rate $limit_rate;
...
}
...
}
在这个例子中,map指令根据请求的URI将请求映射到不同的限速值,然后将这些限速值赋给变量limit_rate。在server块中的location配置中,使用limit_rate指令将limit_rate变量应用于限速设置,从而实现了根据请求路径动态限速的功能。
通过这种方式,你可以根据不同的需求和场景,灵活地调整请求的发送速率,以达到更好的性能和资源利用率。
基于时间动态限速
利用Nginx的ssi
模块中提供的时间变量以及正则表达式,结合map
指令和限速设置,实现根据不同的时间段动态调整限速的功能。
假设我们希望在白天和晚上分别设置不同的限速,可以按照以下步骤操作:
- 首先,使用
ssi
模块中提供的$date_local
变量获取当前本地时间。 - 利用正则表达式匹配当前时间是否在白天或晚上。
- 结合map指令,根据匹配结果将不同的时间段映射到不同的限速值。
- 将这些限速值作为变量传递给
limit_rate
指令,实现动态限速。
以下是一个简单的Nginx配置示例:
代码语言:javascript复制map $date_local $limit_rate {
~* "^d{4}-d{2}-d{2} 0[8-1]d:d{2}:d{2}" 1m; # 早上8点到晚上1点,设置限速为1MB/s
default 500k; # 其他时间段,设置限速为500KB/s
}
server {
...
location / {
ssi on;
limit_rate $limit_rate;
...
}
...
}
在这个示例中,map指令根据当前本地时间date_local进行正则表达式匹配,将匹配结果映射到不同的限速值。如果当前时间匹配了早上8点到晚上1点之间的时间段,则限速设置为1MB/s;否则,默认限速为500KB/s。然后,将这些限速值作为变量limit_rate传递给limit_rate指令,实现动态限速。
这样一来,Nginx就能根据当前的时间段自动调整限速,实现了动态限速的功能。
基于变量动态限速
利用Nginx的变量和map指令结合cookie来实现对不同用户的动态限速。你
首先,已经定义了一个map
变量$limit_rate_cookie
,根据不同的用户cookie值映射到不同的限速值。接着, 使用了limit_rate指令将这个变量应用于限速设置。
map $cookie_User $limit_rate_cookie {
gold 64k;
silver 32k;
copper 16k;
iron 8k;
}
server {
...
location / {
limit_rate $limit_rate_cookie;
...
}
...
}
在这个示例中,根据用户的cookie值来映射不同的限速值。例如,如果用户的cookie中包含"User=gold",则限速为64KB/s;如果是"User=silver",则限速为32KB/s,依此类推。
这样一来,Nginx会根据用户的cookie值动态调整限速,实现了对不同用户的个性化限速。