make
sudo make install
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
这里首先使用了limit_req_zone指令定义了一个限速zone,名为mylimit,大小为10MB,对应的变量是$binary_remote_addr,也就是同一个远程IP算一组请求,限制的请求速率是每秒限制10个请求(10requests/secends),在login这个location中使用limit_req指令指定了限制的zone。接下来我们详细解析一下整个限速的过程:
首先是limit_req_zone指令主要用于定义速度限制相关的参数,而limit_req指令则是用于启用定义的限速参数(如这里是在login中启用)
limit_req_zone 指令一般用于http块中,使其可以在多个相关server、location等contexts中使用,一般来说它需要定义下面三个必要参数:
Syntax:limit_req_zone key zone=name:size rate=rate [sync];
Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];
key:定义用于限制请求的变量,在这个示例中使用的是NGINX的自带变量$binary_remote_addr,它的特点是使用二进制来表示IP地址,如123.183.224.65这个IP在$remote_addr中显示为123.183.224.65,在$binary_remote_addr表示为{\xB7\xE0A,因此$binary_remote_addr占用的空间要比$remote_addr更少。使用$binary_remote_addr则意味着将每个唯一的用户IP作为限制速率的判断依据。
zone:定义用于存储前面定义的key变量和限制其访问请求频率rate变量的共享内存空间,将信息保存在共享内存中的好处是能够在多个worker进程中共享。存储空间的定义由两个部分组成:zone=后面的名称以及冒号后面的大小,如zone=mylimit:10m 就是一个名为mylimit的大小为10m的共享内存空间。以$binary_remote_addr 变量为例,它使用4 bytes来存储IPv4 地址或者是使用16 bytes来存储IPv6地址。存储状态始终在32位平台上占用64个字节,并在64位平台上占用128个字节。考虑到现在的服务器绝大多数都是64位的操作系统,1M的大小可以保留大约8192个128字节的状态。
当存储空间耗尽的时候,如果需要记录新的值,那么就会通过LRU算法移除旧的变量来腾出空间,如果这样腾出来的空间还是不足以接纳新的记录值,那么nginx就会返回状态码503 (Service Temporarily Unavailable)。此外,为了防止内存耗尽,nginx每次创建一个新记录值的时候就会清理掉两个60秒内没被使用过的旧记录值。
If the zone storage is exhausted, the least recently used state is removed. If even after that a new state cannot be created, the request is terminated with an error.
rate:设定允许的最大请求速率。上面的例子是每秒十个请求(10r/s)。nginx实现的是毫秒级别的控制粒度,10r/s对应的就是1r/100ms,这也就意味着在没有设置bursts的情况下,如果一个请求接受处理之后的100ms内出现第二个请求,那么它就会被拒绝处理。
limit_req_zone指令设置了速率限制和共享内存区域的参数,但它实际上并不限制请求速率。因此我们需要通过在contexts中使用limit_req指令来将其限制应用于特定location或server块。在上面的例子里,我们将请求速率限制在/login/这个location块中。因此现在每个唯一的 IP 地址被限制为每秒 10 个**/login/**请求,或者更准确地说,不能在前一个 URL 请求的 100 毫秒内发出对该 URL 的第二次请求。
4、突发请求处理(Bursts)
上面的基础配置只能处理最简单的理想情况,但是如果服务器在100毫秒内收到了2个及以上的请求,那么在上面的配置中,nginx就会向第1个请求之后的所有客户端返回503代码。考虑到并发是程序的天然属性,大多数情况下都是同一时间内涌入大量的请求,因此这显然并不是我们想要的处理方案,我们想要的应该是尽可能“均匀平滑”地处理所有的请求而不是直接拒绝掉它们。因此在这种情况下我们可以使用burst参数设置突发阈值,允许并发情况的处理。
location /login/ {
limit_req zone=mylimit burst=20;
proxy_pass http://my_upstream;
}
上面这段配置中我们设置了burst=20,该配置定义了客户端可以超过区域指定速率的请求数(对于我们前面定义的mylimit区域,请求速率限制为每秒 10 个请求即每 100 毫秒 1 个)。在前一个请求之后 100 毫秒内到达的请求会被放入到队列中,这里我们将队列大小设置为 20。
也就是说如果有22个请求同时发送过来,那么NGINX会马上把第1个请求根据相关规则转发给upstream服务器,然后把接下来的第2到21共计20个请求放入队列中,接着直接返回503代码给第22个请求,随后的2秒时间内,每100毫秒从队列中取出一个请求发送给upstream服务器进行处理。
5、无延迟队列(Queueing with No Delay)
上面的方法虽然使得请求的流量变得**“均匀平滑”**,但是确很大程度上增加了响应时间,排在队列越后面的请求的等待时间越长,这就导致了它们的响应时间平白无故地增加了许多,过长的响应时间甚至可能会导致客户端认为请求异常或者直接导致请求超时。为了解决这种情况,我们可以在brust参数后面加上nodelay参数。
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
加上了nodelay参数之后,nginx的处理方式和上面基本相同,唯一的区别在于:当nginx接受了第2到21共计20个请求之后,不会把它们放入队列中,而是直接将它们转发给upstream服务器,同时标记队列中的这20个插槽(slot)为已使用,然后把剩下的全部请求都503拒绝掉,接着每过100毫秒再释放一个新的slot让新的请求进来。
With the nodelay parameter, NGINX still allocates slots in the queue according to the burst parameter and imposes the configured rate limit, but not by spacing out the forwarding of queued requests.
Instead, when a request arrives “too soon”, NGINX forwards it immediately as long as there is a slot available for it in the queue. It marks that slot as “taken” and does not free it for use by another request until the appropriate time has passed (in our example, after 100ms).
现在假设在第一组请求转发后 101 毫秒,另外 20 个请求同时到达。队列中只有 1 个插槽已被释放,因此 NGINX 转发 1 个请求给upstream服务器并以 status 503 拒绝其他 19 个请求。如果在 20 个新请求到达之前 已经过去了501毫秒而不是101毫秒,则有 5 个空闲槽,因此 NGINX 立即转发 5 个请求upstream服务器并拒绝剩余15 个请求。*这样最终的效果相当于每秒 10 个请求的速率限制,只不过没有了前面的“均匀平滑”*的特性,但是却有效降低了响应时间。**因此如果我们需要在不限制每个请求之间的时间间隔的情况下限制请求速率,可以考虑使用nodelay参数。
Note: For most deployments, we recommend including the burst and nodelay parameters to the limit_req directive.
6、两段限速(Two-Stage Rate Limiting)
6.1 原理解析
简单来说,所谓的分段限速就是允许客户端在刚开始的时候有一定的突发请求,后面再进入到平稳的限速中。
我们可以在NGINX Plus R17或者是NGINX 1.15.7使用limit_req 指令和delay参数来实现两段限速,delay参数将nginx配置为允许突发请求以适应典型的 Web 浏览器请求模式,然后将额外的过度请求限制到一定程度,超过该点的额外过度请求将被拒绝。
这里我们以5r/s的限制速率为例,一般来说网站通常每个页面有 4 到 6 个资源,并且永远不会超过 12 个资源。该配置允许最多 12 个请求的突发,其中前 8 个请求会被直接转发给upstream处理。在达到5r/s的请求限制之后,第6到第13个请求会被添加到延迟(delay)中,再之后的任何请求都会被拒绝。
limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;
server {
listen 80;
location / {
limit_req zone=ip burst=12 delay=8;
proxy_pass http://website;
}
}
下面假设有一个客户端不断地向我们的限速服务器发出请求,根据上面的配置,nginx的处理情况如下:
这里可以看到,从burst队列中首先最开始的12个请求可以按照配置分为8+5
即最前面的8个请求会被直接发送给upstream处理,也就是在burst队列中的no delay部分,这里的8个和配置中的参数delay=8吻合
随后的5个请求也会被加入到burst队列中,这里的处理规则就不是按照前面的no delay部分的规则来处理,而是先按照设定的rate=5r/s来进行处理,接着同样是这一秒内的其他请求都会被返回503代码拒绝掉
再进入下一秒的时间,这里的请求就和之前设定的一样,全部按照rate=5r/s来进行处理,同样是这一秒内的其他请求都会被返回503代码拒绝掉
首先是limit_req_zone指令主要用于定义速度限制相关的参数,而limit_req指令则是用于启用定义的限速参数(如这里是在login中启用)
limit_req_zone 指令一般用于http块中,使其可以在多个相关server、location等contexts中使用,一般来说它需要定义下面三个必要参数:
Syntax:limit_req_zone key zone=name:size rate=rate [sync];
Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];
key:定义用于限制请求的变量,在这个示例中使用的是NGINX的自带变量$binary_remote_addr,它的特点是使用二进制来表示IP地址,如123.183.224.65这个IP在$remote_addr中显示为123.183.224.65,在$binary_remote_addr表示为{\xB7\xE0A,因此$binary_remote_addr占用的空间要比$remote_addr更少。使用$binary_remote_addr则意味着将每个唯一的用户IP作为限制速率的判断依据。
zone:定义用于存储前面定义的key变量和限制其访问请求频率rate变量的共享内存空间,将信息保存在共享内存中的好处是能够在多个worker进程中共享。存储空间的定义由两个部分组成:zone=后面的名称以及冒号后面的大小,如zone=mylimit:10m 就是一个名为mylimit的大小为10m的共享内存空间。以$binary_remote_addr 变量为例,它使用4 bytes来存储IPv4 地址或者是使用16 bytes来存储IPv6地址。存储状态始终在32位平台上占用64个字节,并在64位平台上占用128个字节。考虑到现在的服务器绝大多数都是64位的操作系统,1M的大小可以保留大约8192个128字节的状态。
当存储空间耗尽的时候,如果需要记录新的值,那么就会通过LRU算法移除旧的变量来腾出空间,如果这样腾出来的空间还是不足以接纳新的记录值,那么nginx就会返回状态码503 (Service Temporarily Unavailable)。此外,为了防止内存耗尽,nginx每次创建一个新记录值的时候就会清理掉两个60秒内没被使用过的旧记录值。
If the zone storage is exhausted, the least recently used state is removed. If even after that a new state cannot be created, the request is terminated with an error.
rate:设定允许的最大请求速率。上面的例子是每秒十个请求(10r/s)。nginx实现的是毫秒级别的控制粒度,10r/s对应的就是1r/100ms,这也就意味着在没有设置bursts的情况下,如果一个请求接受处理之后的100ms内出现第二个请求,那么它就会被拒绝处理。
limit_req_zone指令设置了速率限制和共享内存区域的参数,但它实际上并不限制请求速率。因此我们需要通过在contexts中使用limit_req指令来将其限制应用于特定location或server块。在上面的例子里,我们将请求速率限制在/login/这个location块中。因此现在每个唯一的 IP 地址被限制为每秒 10 个**/login/**请求,或者更准确地说,不能在前一个 URL 请求的 100 毫秒内发出对该 URL 的第二次请求。
4、突发请求处理(Bursts)
上面的基础配置只能处理最简单的理想情况,但是如果服务器在100毫秒内收到了2个及以上的请求,那么在上面的配置中,nginx就会向第1个请求之后的所有客户端返回503代码。考虑到并发是程序的天然属性,大多数情况下都是同一时间内涌入大量的请求,因此这显然并不是我们想要的处理方案,我们想要的应该是尽可能“均匀平滑”地处理所有的请求而不是直接拒绝掉它们。因此在这种情况下我们可以使用burst参数设置突发阈值,允许并发情况的处理。
location /login/ {
limit_req zone=mylimit burst=20;
proxy_pass http://my_upstream;
}
上面这段配置中我们设置了burst=20,该配置定义了客户端可以超过区域指定速率的请求数(对于我们前面定义的mylimit区域,请求速率限制为每秒 10 个请求即每 100 毫秒 1 个)。在前一个请求之后 100 毫秒内到达的请求会被放入到队列中,这里我们将队列大小设置为 20。
也就是说如果有22个请求同时发送过来,那么NGINX会马上把第1个请求根据相关规则转发给upstream服务器,然后把接下来的第2到21共计20个请求放入队列中,接着直接返回503代码给第22个请求,随后的2秒时间内,每100毫秒从队列中取出一个请求发送给upstream服务器进行处理。
5、无延迟队列(Queueing with No Delay)
上面的方法虽然使得请求的流量变得**“均匀平滑”**,但是确很大程度上增加了响应时间,排在队列越后面的请求的等待时间越长,这就导致了它们的响应时间平白无故地增加了许多,过长的响应时间甚至可能会导致客户端认为请求异常或者直接导致请求超时。为了解决这种情况,我们可以在brust参数后面加上nodelay参数。
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
加上了nodelay参数之后,nginx的处理方式和上面基本相同,唯一的区别在于:当nginx接受了第2到21共计20个请求之后,不会把它们放入队列中,而是直接将它们转发给upstream服务器,同时标记队列中的这20个插槽(slot)为已使用,然后把剩下的全部请求都503拒绝掉,接着每过100毫秒再释放一个新的slot让新的请求进来。
With the nodelay parameter, NGINX still allocates slots in the queue according to the burst parameter and imposes the configured rate limit, but not by spacing out the forwarding of queued requests.
Instead, when a request arrives “too soon”, NGINX forwards it immediately as long as there is a slot available for it in the queue. It marks that slot as “taken” and does not free it for use by another request until the appropriate time has passed (in our example, after 100ms).
现在假设在第一组请求转发后 101 毫秒,另外 20 个请求同时到达。队列中只有 1 个插槽已被释放,因此 NGINX 转发 1 个请求给upstream服务器并以 status 503 拒绝其他 19 个请求。如果在 20 个新请求到达之前 已经过去了501毫秒而不是101毫秒,则有 5 个空闲槽,因此 NGINX 立即转发 5 个请求upstream服务器并拒绝剩余15 个请求。*这样最终的效果相当于每秒 10 个请求的速率限制,只不过没有了前面的“均匀平滑”*的特性,但是却有效降低了响应时间。**因此如果我们需要在不限制每个请求之间的时间间隔的情况下限制请求速率,可以考虑使用nodelay参数。
Note: For most deployments, we recommend including the burst and nodelay parameters to the limit_req directive.
6、两段限速(Two-Stage Rate Limiting)
6.1 原理解析
简单来说,所谓的分段限速就是允许客户端在刚开始的时候有一定的突发请求,后面再进入到平稳的限速中。
我们可以在NGINX Plus R17或者是NGINX 1.15.7使用limit_req 指令和delay参数来实现两段限速,delay参数将nginx配置为允许突发请求以适应典型的 Web 浏览器请求模式,然后将额外的过度请求限制到一定程度,超过该点的额外过度请求将被拒绝。
这里我们以5r/s的限制速率为例,一般来说网站通常每个页面有 4 到 6 个资源,并且永远不会超过 12 个资源。该配置允许最多 12 个请求的突发,其中前 8 个请求会被直接转发给upstream处理。在达到5r/s的请求限制之后,第6到第13个请求会被添加到延迟(delay)中,再之后的任何请求都会被拒绝。
limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;
server {
listen 80;
location / {
limit_req zone=ip burst=12 delay=8;
proxy_pass http://website;
}
}
下面假设有一个客户端不断地向我们的限速服务器发出请求,根据上面的配置,nginx的处理情况如下:
这里可以看到,从burst队列中首先最开始的12个请求可以按照配置分为8+5
即最前面的8个请求会被直接发送给upstream处理,也就是在burst队列中的no delay部分,这里的8个和配置中的参数delay=8吻合
随后的5个请求也会被加入到burst队列中,这里的处理规则就不是按照前面的no delay部分的规则来处理,而是先按照设定的rate=5r/s来进行处理,接着同样是这一秒内的其他请求都会被返回503代码拒绝掉
再进入下一秒的时间,这里的请求就和之前设定的一样,全部按照rate=5r/s来进行处理,同样是这一秒内的其他请求都会被返回503代码拒绝掉