限制对nginx服务资源的使用
2019-04-20 10:02:44 阿炯

Nginx中用来限制用户在给定时间内HTTP请求的数量,可以是一个简单网站首页的GET请求,也可以是登录表单的 POST 请求。流量限制可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),可以用来抵御 DDOS或CC攻击。


nginx中使用ngx_http_limit_conn_module与ngx_http_limit_req_module模块的配合使用来实现此目的,前者针对的是连接(connection),而后者针对的是请求(request)。request和connection区别如下:

在nginx里面,limit_req和limit_conn都是用来限流的但是两者不在一个层次上,在开始之前,需要先清楚request和connect的区别:request是请求,是http层面的。connection是链接,指的是tcp层面。所以,从含义上面可以知道两者不在一个层次。

我们在打开一个网页的时候,请求过程一般就是先经过tcp三次握手,然后在进行http请求。也就是一个connection建立之后,可以有很多个的request;一个connection建立,只要服务器处理能力够强,可以处理任意多的request都是没有问题的。

* ngx_http_limit_req_module:限制单个IP单位时间内的请求数,即速率限制,该模块默认已经安装

* ngx_http_limit_conn_module:限制单个IP同一时间连接数,即并发连接数限制

Nginx的速率限制使用漏桶算法(leaky bucket algorithm),该算法在通讯和分组交换计算机网络中广泛使用,用以处理带宽有限时的突发情况。就好比向桶口在倒水,桶底在漏水的水桶。如果桶口倒水的速率大于桶底的漏水速率,桶里面的水将会溢出;同样,在请求处理方面,水代表来自客户端的请求,水桶代表根据先进先出调度算法(FIFO)等待被处理的请求队列,桶底漏出的水代表离开缓冲区被服务器处理的请求,桶口溢出的水代表被丢弃和不被处理的请求。


ngx_http_limit_conn_module模块指令

用ngx_http_limit_conn_module模块来限制连接数,需要通过配合ngx_http_limit_req_module模块来实现,该模块可以通过定义的键值来限制请求处理的频率。特别的,可以限制来自单个IP地址的请求处理频率。限制的方法如同漏斗,每秒固定处理请求数,推迟过多请求。

ngx_http_limit_conn_module对同一个ip的连接数,并发数进行限制。该模块可以根据定义的键来限制每个键值的连接数,如同一个IP来源的连接数。并不是所有的连接都会被该模块计数,只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数。

ngx_http_limit_conn_module:限制IP连接数

ngx_http_limit_conn_module的模块也有2部分:
* http字段中定义触发条件,可以有多个条件;
* server、location段中定义达到触发条件时nginx所要执行的动作。


http段:
语法:limit_conn_zone key zone=name:memory_max_size;
实例:limit_conn_zone $binary_remote_addr zone=one:10m;
说明:limit_conn_zone模块只能配置在http字段中进行定义,该指令描述会话状态存储区域。主要用来定义变量、zone名称、共享内存大小,键的状态中保存了当前连接数,键的值可以是特定变量的任何非空值(空值不会被考虑)。size 定义各个键共享内存空间大小,如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。从Nginx 1.1.8版本后limit_conn升级为limit_conn_zone。连接数限制不是所有的连接都计算在内;只有那些已请求该服务器并当前正在处理的请求(请求头已充分阅读的)。

server, location段:
1、limit_conn one
语法:limit_conn one conn_max_num;
实例:limit_conn one 20;
说明:该指令指定每个给定键值的最大同时连接数,当超过这个数字时返回503(Service )错误。如(同一IP同一时间只允许有20个连接)。

http {
limit_conn_zone  $binary_remote_addr  zone=one:10m;
server {
 listen 80;
 limit_conn one 20;
 }
}   

还可以叠加使用,同时限制访问的虚拟主机:

http {
limit_conn_zone  $binary_remote_addr  zone=perip:100m;
limit_conn_zone  $server_name  zone=perserver:100m;

server {
 listen 80;
 limit_conn perip 50;; #同一个IP限制链接20个
 limit_conn perserver 20;;   #同一主机名链接限制在100个
 }
}   

2、 limit_conn_log_level
语法: limit_conn_log_level info | notice | warn | error;
默认值: limit_conn_log_level error;
使用环境: http, server, location
设置触发最大限制后记录日志的级别,默认为error级别,该指令在 0.8.18版后新增

3、 limit_conn_status
语法: limit_conn_status code;
默认值: limit_conn_status 503;
使用环境: http, server, location
当超出最大同时连接数的时候,对于新增连接返回的错误代码,默认503. 该指令在 1.3.15版本后新增,

4、 limit_rate
语法:limit_rate rate
默认值:0
实例:limit_rate 200k;
配置段:http, server, location, if in location
对每个连接的速率限制。参数rate的单位是字节/秒,设置为0将关闭限速。按连接限速而不是按IP限制,因此如果某个客户端同时开启了两个连接,那么客户端的整体速率是这条指令设置值的2倍。

上述指令没有看明白,那么再分开各个指令讲一次(示例可能有重复)。


limit_conn_zone
语法: limit_conn_zone $variable zone=name:size;
默认值: none
配置段: http

该指令描述会话状态存储区域。键的状态中保存了当前连接数,键的值可以是特定变量的任何非空值(空值将不会被考虑)。$variable定义键,zone=name定义区域名称,后面的limit_conn指令会用到的。size定义各个键共享内存空间大小。如:
limit_conn_zone $binary_remote_addr zone=addr:10m;

注释:客户端的IP地址作为键。注意,这里使用的是$binary_remote_addr变量,而不是$remote_addr变量。
$remote_addr变量的长度为7字节到15字节,而存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。
$binary_remote_addr变量的长度是固定的4字节,存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。
1M共享空间可以保存3.2万个32位的状态,1.6万个64位的状态。
如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。
limit_zone 指令和limit_conn_zone指令同等意思,已经被弃用,就不再做说明了。


limit_conn_log_level
语法:limit_conn_log_level info | notice | warn | error
默认值:error
配置段:http, server, location

当达到最大限制连接数后,记录日志的等级。

limit_conn
语法:limit_conn zone_name number
默认值:none
配置段:http, server, location
指定每个给定键值的最大同时连接数,当超过这个数字时被返回503 (Service Temporarily Unavailable)错误。如:
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
    location /www.freeoa.net/ {
        limit_conn addr 1;
    }
}

limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
    location /www.freeoa.net/ {
        limit_conn addr 1;
    }
}

同一IP同一时间只允许有一个连接。当多个 limit_conn 指令被配置时,所有的连接数限制都会生效。比如,下面配置不仅会限制单一IP来源的连接数,同时也会限制单一虚拟服务器的总连接数:
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    limit_conn perip 10;
    limit_conn perserver 100;
}

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    limit_conn perip 10;
    limit_conn perserver 100;
}

limit_conn指令可以从上级继承下来。

limit_conn_status
语法: limit_conn_status code;
默认值: limit_conn_status 503;
配置段: http, server, location

该指定在1.3.15版本引入的。指定当超过限制时,返回的状态码。默认是503。

limit_rate
语法:limit_rate rate
默认值:0
配置段:http, server, location, if in location
对每个连接的速率限制。参数rate的单位是字节/秒,设置为0将关闭限速。 按连接限速而不是按IP限制,因此如果某个客户端同时开启了两个连接,那么客户端的整体速率是这条指令设置值的2倍。

完整实例配置

http {
    limit_conn_zone $binary_remote_addr zone=limit:10m;
    limit_conn_log_level info;

    server {
        location  ^~ /download/ {  
            limit_conn limit 4;
            limit_rate 200k;
            alias /data/www.freeoa.net/download/;
        }
    }
}


ngx_http_limit_req_module模块指令

ngx_http_limit_req_module:限制每秒请求数

ngx_http_limit_req_module模块通过漏桶原理来限制单位时间内的请求数,一旦单位时间内请求数超过限制,就会返回503错误。该模块有2部分:
* http字段中定义触发条件,可以有多个条件;
* server、location段中定义达到触发条件时nginx所要执行的动作。


http段:
语法:limit_req_zone $variable zone=name:size rate=rate;
实例:limit_req_zone $binary_remote_addr zone=two:100m rate=50r/s;
说明:
1)、$binary_remote_addr:是$remote_addr(客户端IP)的二进制格式,固定占用4个字节,而$remote_addr按照字符串存储,占用7-15个字节,用$binary_remote_addr可以节省空间。这样1M的内存可以保存大约1万6千个64字节的记录。
2)、zone=:区域名称,可自定义,这里写的是two,分配内存空间大小为100m,,用来存储会话(二进制远程地址),如果限制域的存储空间耗尽了,对于后续所有请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。
3)、rate=:平均处理的请求数,这里定义的是每秒不能超过每秒50次。也可以设置为每分钟处理请求数(r/m),其值必须是整数。

limit_req_zone指令一般定义在http块内部,使得该指令可以在多个环境中使用,该指令有下面三个参数(接上详述):

Key -- 在限流应用之前定义了请求的特征。在上面例子中,它是$binary_remote_addr(Nginx变量),该变量代表了某个客户端IP地址的二进制形式。这意味着我们可以将每个特定的IP地址的请求速率限制为第三个参数所定义的值(使用这个变量的原因是因为它比用string代表客户端IP地址的$remote_addr变量消耗更少的空间。)

Zone -- 定义了存储每个IP地址状态和它访问受限请求URL的频率的共享内存区域。将这些信息保存在共享内存中,意味着这些信息能够在Nginx工作进程之间共享。定义有两个部分:由zone=关键字标识的区域名称,以及冒号后面的区域大小。约16000个IP地址的状态信息消耗1M内存大小,因此我们的区域(zone)大概可以存储约160000个地址。

当Nginx需要添加新的记录时,如果此时存储耗尽了,最老的记录会被移除。如果释放的存储空间还是无法容纳新的记录,Nginx返回503 (Service Temporarily Unavailable)状态码。为了防止内存被耗尽,每次Nginx创建一个新的记录的同时移除多达两条前60秒内没有被使用的记录。

Rate -- 设置最大的请求速率。在上面的例子中,速率不能超过10个请求每秒。Nginx事实上可以在毫秒级别追踪请求,因此这个限制对应了1个请求每100毫秒。因为我们不允许突发(bursts,短时间内的突发流量,下有详述),这意味着如果某个请求到达的时间离前一个被允许的请求小于100毫秒,它会被拒绝。

limit_req_zone指令设置限流和共享内存区域的参数,但是该指令实际上并不限制请求速率。为了限制起作用,需要将该限制应用到某个特定的location或server块(block),通过包含一个limit_req指令的方式。


server, location段:
1、limit_req zone
语法: limit_req zone=name [burst=number] [nodelay];
实例:limit_req zone=two burst=20 nodelay;
说明:
1)、zone=:和http字段中limit_req_zone的zone定义必须一致
2)、burst=:可以理解为缓冲队列
3)、nodelay:对用户发起的请求不做延迟处理,而是立即处理。比如上面定义了rate=20r/s,即每秒钟只处理20个请求。如果同一时刻有22个请求过,若设置了nodelay,则会立刻处理这22个请求。若没设置nodelay,则会严格执行rate=20r/s的配置,即只处理20个请求,然后下一秒钟再处理另外2个请求。直观的看就是页面数据卡了,过了一秒后才加载出来。

http {
......
limit_req_zone $binary_remote_addr zone=two:100m rate=50r/s;
server {
listen 80;
limit_req zone=two burst=20 nodelay;
}
}

对限流起作用的配置就是rate=50r/s和burst=20这两个配置,假如同一秒有55个请求到达nginx,其中50个请求被处理,另外的5个请求被放到burst缓冲队列里,由于配置了nodelay,第51-55个请求依然会被处理,但是会占用burst缓冲队列的5个长度,如果下一秒没有新的请求过来,这5个长度的空间会被释放,否则会继续占用burst缓冲队列5个长度,如果后面每秒请求都超过50个,那么多余的请求虽然会被处理但是会占用burst缓冲队列长度,直到达到设定的burst=20,后面的每秒第51个请求开始会被nginx拒绝,并返回503错误,被拒绝的请求在nginx的日志中可以看到被那个zone拒绝的。

2、limit_req_log_level
语法: limit_req_log_level info | notice | warn | error;
默认值: limit_req_log_level error;
配置段: http, server, location
设置日志级别,当服务器因为频率过高拒绝或者延迟处理请求时可以记下相应级别的日志。 延迟记录的日志级别比拒绝的低一个级别; 如果设置“limit_req_log_level notice”, 延迟的日志就是info级别。

3、limit_req_status
语法: limit_req_status code;
默认值: limit_req_status 503;
配置段: http, server, location
该指令在1.3.15版本引入。设置拒绝请求的响应状态码。

上述指令没有看明白,那么再分开各个指令讲一次。


limit_req_zone

语法: limit_req_zone $variable zone=name:size rate=rate;
默认值: none
配置段: http
设置一块共享内存限制域用来保存键值的状态参数。 特别是保存了当前超出请求的数量。 键的值就是指定的变量(空值不会被计算)。如

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

说明:区域名称为one,大小为10m,平均处理的请求频率不能超过每秒一次。
键值是客户端IP。使用$binary_remote_addr变量, 可以将每条状态记录的大小减少到64个字节,这样1M的内存可以保存大约1万6千个64字节的记录。如果限制域的存储空间耗尽了,对于后续所有请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。速度可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,所以如果你需要指定每秒处理少于1个的请求,2秒处理一个请求,可以使用 "30r/m"。

limit_req_log_level

语法: limit_req_log_level info | notice | warn | error;
默认值: limit_req_log_level error;
配置段: http, server, location
设置你所希望的日志级别,当服务器因为频率过高拒绝或者延迟处理请求时可以记下相应级别的日志。 延迟记录的日志级别比拒绝的低一个级别;比如, 如果设置"limit_req_log_level notice", 延迟的日志就是info级别。

limit_req_status

语法: limit_req_status code;
默认值: limit_req_status 503;
配置段: http, server, location

该指令在1.3.15版本引入,设置拒绝请求的响应状态码。

limit_req

语法: limit_req zone=name [burst=number] [nodelay];
默认值: -
配置段: http, server, location
设置对应的共享内存限制域和允许被处理的最大请求数阈值。如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以所有的请求都是以定义的频率被处理的。超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阈值,这时,这个请求会被终止,并返回503 (Service Temporarily Unavailable) 错误。这个阈值的默认值为0。如:

limit_req_zone $binary_remote_addr zone=freeoa_net:10m rate=1r/s;
server {
location /app/ {
    limit_req zone=freeoa_net burst=5;
}
}

限制平均每秒不超过一个请求,同时允许超过频率限制的请求数不多于5个。如果不希望超过的请求被延迟,可以用nodelay参数,如:
limit_req zone=freeoa_net burst=5 nodelay;


其它参数说明

处理流量突发(Bursts)
如果我们在1000毫秒内接收到2个请求该怎么办?对于第二个请求,Nginx将给客户端返回错误。这可能并不是我们想要的结果,因为应用本质上趋向于突发性。相反,我们希望缓冲任何超额的请求,然后及时地处理它们。更新下配置,在limit_req中使用burst参数:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
upstream myweb {
    server 192.168.62.17:80 weight=1 max_fails=1 fail_timeout=1;
}

server {
listen 80;
server_name localhost;
location /login {
    limit_req zone=mylimit burst=20;
    proxy_pass http://myweb;
    proxy_set_header Host $host:$server_port;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

burst参数定义了超出zone指定速率的情况下(示例中的mylimit区域,速率限制在每秒10个请求,或每100毫秒一个请求),客户端还能发起多少请求。上一个请求100毫秒内到达的请求将会被放入队列,我们将队列大小设置为20。这意味着,如果从一个给定IP地址发送21个请求,Nginx会立即将第一个请求发送到上游服务器群,然后将余下20个请求放在队列中。然后每100毫秒转发一个排队的请求,只有当传入请求使队列中排队的请求数超过20时,Nginx才会向客户端返回错误。

无延迟排队
带有burst的配置产生平滑的网络流量,但是不实用,因为该配置会使得你的网站表现的很慢。在上面的例子中,队列中第20个数据包等待2秒才能被转发,这时该数据包的响应可能对于客户端已经没有了意义。为了处理这种情况,除了burst参数外,添加nodelay参数。
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream
}

带有nodelay参数,Nginx仍然会按照burst参数在队列中分配插槽(slot)以及利用已配置的限流,但是不是通过间隔地转发队列中的请求。相反,当某个请求来的太快,只要队列中有可用的空间(slot),Nginx会立即转发它。该插槽(slot)被标记为“已使用”,并且不会被释放给另一个请求,一直到经过适当的时间(在上面的例子中,是100毫秒)。

像之前一样假设有20个插槽的队列是空的,并且来自于给定的IP地址的21个请求同时地到达。Nginx立即转发这21个请求以及将队列中的20个插槽标记为“已使用”,然后每隔100毫秒释放一个插槽。(相反,如果有25个请求,Nginx会立即转发25个中的21个请求,标记20个插槽为“已使用”,并且用503状态拒绝4个请求。)

现在假设在转发第一个请求集合之后的101毫秒,有另外的20个请求同时地到达。队列中只有1个插槽被释放,因此Nginx转发1个请求,并且用503状态拒绝其它的19个请求。相反,如果在这20个新请求到达之前过去了501毫秒,则有5个插槽被释放,因此Nginx立即转发5个请求,并且拒绝其它15个请求。

效果等同于10个请求每秒的限流。如果你想利用请求之间的无限制性间隔的限流,nodelay选项则是非常有用的。

注意:对于大多数的部署,我们推荐在limit_req指令中包含burst和nodelay参数。

日志(Logging)
默认,NGNIX记录由于限流导致的延迟或丢弃的请求的日志,如下面的例子:
2018/05/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.9.2, server: freeoa.net, request: "GET / HTTP/1.0", host: "freeoa.net"

该日志记录包含的字段:
limiting requests -- 日志条目记录了某个限流的标志

excess -- 超过这个请求代表的配置的速率的每毫秒请求数目

zone -- 定义了启用了限流的区域

client -- 产生请求的客户端IP地址

server -- 服务器的IP地址或主机名

request -- 客户端产生的实际的HTTP请求

host -- HTTP头部主机名的值

Nginx默认日志在error级别拒绝请求,如上面例子中的[error]所示(它在低一个级别上记录延迟的请求,因此默认是info),用limit_req_log_level指令来改变日志级别。下面我们设置在warn级别上记录被拒绝的请求的日志:
location /login/ {
    limit_req zone=mylimit burst=20 nodelay;
    limit_req_log_level warn;
    proxy_pass http://my_upstream;
}

定制发送给客户端的错误码
默认当某个客户端超过它的限流,Nginx用503(Service Temporarily Unavailable)状态码来响应。使用limit_req_status指令设置一个不同的状态码(在下面的例子是444):
location /login/ {
    limit_req zone=mylimit burst=20 nodelay;
    limit_req_log_level warn;
    limit_req_status 444;
}


完整实例配置

http {
limit_req_zone $binary_remote_addr zone=freeoa_net:10m rate=1r/s;

server {
location  ^~ /download/ {  
limit_req zone=freeoa_net burst=5;
alias /data/www.freeoa.com/download/;
}}
}

limit_rate limit_conn限制文件下载速度

使用Nginx的limit_rate和limit_rate_after这两个指令解决此问题,在一个location块使用这两个指令:
location ^~ /videos/ {
...
limit_rate_after 1m;
limit_rate 150k;
...
}

第一个指令limit_rate_after,从下载到你指定的文件大小之后开始限速,然后第二个指令limit_rate用于设置最高下载速度。

要注意的是上面的设置是限制的是每一个连接的下载速度,所以如果一个用户打开了多个连接下载,那么它的下载速度就能达到单个连接的限速乘以连接数。不过我们可以使用limit_zone和limit_conn这两个指令限制其连接数。例如:

在server块配置中:
limit_rate 128K;
limit_zone one $binary_remote_addr 10m;

在location配置块中:
limit_conn one 10;

上面的配置表示每个连接允许的速率为1Mbit,最大连接数10个。


关于limit_req和limit_conn的区别

limit_conn

http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_conn one 1; #最多可以进行1个connection
    client_body_buffer_size 128M;
}

上面的配置文件的含义很明白,nginx只接受最多一个connect,我们使用ab命令查看下

$ ab -n10  http://192.168.8.1/ #默认通过一个connect送10个request

看一下nginx里面的日志
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:45:11 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"

在一个并发下,处理10个request下完全没有问题。接下来我们使用2个并发,2个请求试下,也就是两个用户,每个用户发送一个request。

$ ab -n2 -c2 http://192.168.8.1/

192.168.8.90 - - [20/Apr/2019:08:50:54 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:50:54 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

由于nginx设置了至多限制一个并发,所以导致两个用户的请求只能有一个能被处理掉,另外一个返回了503。

limit_req


下面更改下nginx.conf配置文件

http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_req_zone $binary_remote_addr zone=req_one:20m rate=1r/s;
    limit_conn one 20;
    limit_req zone=req_one burst=5;
}

上面的配置的含义是请求速率限制为1r/s,然后再缓存5个request,然后再依次处理请求(令牌桶算法)

$ ab -n10 -c10 http://192.168.8.1/

nginx日志如下:
192.168.8.90 - - [20/Apr/2019:08:58:02 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:02 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:02 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:02 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:02 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:03 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:04 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:05 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:06 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:08:58:07 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"

使用压测命令,10个并发,10个请求,根据配置的文件,当有请求过来,nginx先处理一个请求,然后将5个请求缓存下来(burst=5),根据设置的速率1r/s进行处理,也就是一共能够处理6个请求,其余的请求则被丢掉。

再继续更改下nginx配置文件(加入了nodelay参数)

http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_req_zone $binary_remote_addr zone=req_one:20m rate=1r/s;
    limit_conn one 20;
    limit_req zone=req_one burst=5 nodelay;
}

增加了nodelay好像和不加不同。这是因为请求过来的时候不会被延迟,处理的总的请求数是burst+1,其余的请求丢弃。

192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 200 1826 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
192.168.8.90 - - [20/Apr/2019:09:21:21 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

同样有4个连接被丢弃,但处理的速度却是非常快的。

总结

request和connect是完全不同层面的含义,一个属于http,一个属于tcp。

limit_req zone=req_zone burst=5 依照在limti_req_zone中配置的rate来处理请求,同时设置了一个大小为5的缓冲队列,在缓冲队列中的请求会等待慢慢处理,超过了burst缓冲队列长度和rate处理能力的请求被直接丢弃,表现为对收到的请求有延时。

limit_req zone=req_zone burst=5 nodelay 依照在limti_req_zone中配置的rate来处理请求,同时设置了一个大小为5的缓冲队列,当请求到来时不会被延迟,对于峰值处理数量之外的请求,直接丢弃。


如果觉得上述的设置方法过于复杂的话,可以考虑使用iptables。

iptables设置

单个IP在60秒内只允许新建20个连接到达web的80端口
iptables -I  INPUT -i eth1 -p tcp -m tcp –dport 80 -m state –state NEW -m recent –update –seconds 60 –hitcount 20 –name DEFAULT –rsource -j DROP

iptables -I  INPUT -i eth1 -p tcp -m tcp –dport 80 -m state –state NEW -m recent –set –name DEFAULT –rsource

单个IP的最大并发连接数为20
iptables  -I INPUT -p tcp –dport 80 -m connlimit  –connlimit-above 20 -j REJECT  

每个IP最多20个初始连接
iptables -I  INPUT -p tcp –syn -m connlimit –connlimit-above 20 -j DROP

这样的好处在网络层就处理了,减轻了nginx主机的一些负担,代价是规则有些过于刚性,对人多的公共出口办公室上网不友好。