Nginx访问限制模块ngx_http_limit_req_module与ngx_http_limit_conn_module(限制高并发防止DDOS攻击)

司空俊悟
2023-12-01

Nginx访问限制 (限制高并发防止DDOS攻击)

参考文档:
http://nginx.org/en/docs/http/ngx_http_limit_req_module.html
http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

ngx_http_limit_conn_module 连接频率限制
ngx_http_limit_req_module 请求频率限制

http协议的连接与请求

HTTP是建立在TCP基础上,在完成HTTP请求需要先建立TCP三次握手(称为TCP连接),在连接的基础上在HTTP请求。

HTTP请求建立在一次TCP连接基础上
一次TCP请求至少产生一次HTTP请求(一个连接可以有多个请求,建立一次三次握手可以发送多个请求)

HTTP协议连接关系
HTTP1.0TCP不能复用
HTTP1.1顺序性TCP复用
HTTP2.0多路复用TCP复用

Nginx请求限制配置:

参考文档:http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

# 必须在全局定义请求限制
Syntax: limit_conn_zone key zone=name:size rate=rate;   #zone的名称大小及速率
Default: -
Context: http

# 引用请求限制
Syntax: limit_conn zone number [burst=number] [nodelay];
Default: -
Context: http,server,location

# http模块配置
limit_req_zone $binary_remote_addr zone=req_zone:1m rate=1r/s;
    > $binary_remote_addr比$remote_addr更节省字节,是限制同一客户端ip地址
    > zone=req_zone:1m表示名称是req_zone的内存区域,用来存储访问的频次信息,1m用来存储session,1m能存储16000个状态
    > rate限制速率为1秒最多1个IP请求,rete的值必须为整数
    
# location模块引用配置
limit_req zone=req_zone;

# location模块引用req_zone配置,请求超过1r/s,剩下的将被延迟处理,burst=3缓冲区大小3,nodelay瞬时提供处理(burst + rate)个请求的能力,多余的直接返回503
limit_req zone=req_zone burst=3 nodelay;

http {
...
limit_req_zone $binary_remote_addr zone=req_zone:1m rate=1r/s;
server {
        listen  80;
        server_name localhost;
        root    /usr/share/nginx/html/www;
        index   index.html;

        location / {
                root    /usr/share/nginx/html/www;
                index   index.html;
                limit_req zone=req_zone;        # 限制请求
                #limit_req zone=req_zone burst=3 nodelay;
        }
}
}

# burst :
#   爆发的意思,这个配置的意思是设置一个大小为5的缓冲区,当有大量请求(爆
#   发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,
#   但是这个等待区里的位置只有3个,超过的请求会直接报503的错误然后返回。

# nodelay :
#    瞬时提供处理(burst + rate)个请求的能力,
#    请求超过(burst + rate)的时候就会直接返回503,永远不存在请求需要等待的情况
#    如果没有设置nodelay,则所有请求会依次等待排队

压测一下请求限制效果

#安装压测工具
[root@web-node2 ~]$ yum install -y httpd-tools

场景一:burst和nodelay都不加的情况

location / {
        root    /usr/share/nginx/html/www;
        index   index.html;
        limit_req zone=req_zone;
}


# 使用ab测试工具,发起10个并发请求
[root@web-node2 ~]# ab -n 10 -c 10 http://192.168.1.17/index.html
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.17 (be patient).....done


Server Software:        nginx/1.14.1
Server Hostname:        192.168.1.17
Server Port:            80

Document Path:          /index.html
Document Length:        6 bytes

Concurrency Level:      10                      # 10并发个请求
Time taken for tests:   0.011 seconds           
Complete requests:      10                      # 总共请求10次
Failed requests:        9                       #失败9次
   (Connect: 0, Receive: 0, Length: 9, Exceptions: 0)
Write errors:           0
Non-2xx responses:      9
Total transferred:      3700 bytes
HTML transferred:       1923 bytes
Requests per second:    907.94 [#/sec] (mean)
Time per request:       11.014 [ms] (mean)      # 11ms就压测结束
Time per request:       1.101 [ms] (mean, across all concurrent requests)
Transfer rate:          328.06 [Kbytes/sec] received

>>>可以看到一共10个请求,9个请求都失败了。且11ms就完成了压测



# 查看 /var/log/nginx/access.log,印证了只有一个请求成功了,其它就是都直接返回了503
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:34:04 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"

场景二:只加burst,不加nodelay的情况

location / {
                root    /usr/share/nginx/html/www;
                index   index.html;
                #limit_req zone=req_zone;        # 限制请求
                limit_req zone=req_zone burst=3;
        }

# 查看当前的tcp连接数
[root@mysql-slave ~]$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 6
[root@mysql-slave ~]$


# 建立10个连接并发10
[root@mysql-slave ~]$ ab -n 10 -c 5 http://192.168.1.17/index.html
....
Server Software:        nginx/1.14.1
Server Hostname:        192.168.1.17
Server Port:            80

Document Path:          /index.html
Document Length:        6 bytes

Concurrency Level:      10                  # 10并发个请求
Time taken for tests:   0.012 seconds
Complete requests:      10                  # 总共请求10次
Failed requests:        6                   # 失败了6次
Total transferred:      3250 bytes
HTML transferred:       1302 bytes
Requests per second:    3.32 [#/sec] (mean)
Time per request:       1505.007 [ms] (mean)
Time per request:       301.002 [ms] (mean, across all concurrent requests)
Transfer rate:          1.05 [Kbytes/sec] received
...

>>>> 可以看到一共10个请求,6个请求都失败了

# ab测试第一秒时ESTABLISHED由4变为6,即建立了2个TCP连接,
# 同时TIME_WAIT=9 表示有服务器端主动断开了9个TCP连接,即9个请求被瞬时拒绝
[root@mysql-slave ~]$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 6
TIME_WAIT 9
[root@mysql-slave ~]$

# ab测试完,
# TIME_WAIT=10 表示有服务器端主动断开了10个TCP连接,增加的1个TIME_WAIT是因为有1个在缓存队列的请求被处理完毕了,所以断开了连接
[root@mysql-slave ~]$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 4
TIME_WAIT 10
[root@mysql-slave ~]$



# 查看 /var/log/nginx/access.log日志
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:54 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:55 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:18:13:56 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"

>>>在18:13:53的时候,可以看到压测第1秒时,成功处理了1个请求,另外有6个请求瞬间返回了503,剩下的4个请求每隔1s处理一次
>>>这是因为设置了burst=3,在服务器接收到10个并发请求后,先处理1个请求,同时将3个请求放入burst缓冲队列中,等待处理,
而超过(burst+1)数量的请求就被直接抛弃了,即直接抛弃了6个请求。缓冲区的剩余3个请求,1秒执行1个。

场景三:加burst,加nodelay的情况

location / {
                root    /usr/share/nginx/html/www;
                index   index.html;
                #limit_req zone=req_zone;        # 限制请求
                limit_req zone=req_zone burst=3 nodelay;    #缓冲区大小为3,nodelay瞬时提供处理(burst + rate)个请求
        }


[root@web-node2 ~]# ab -n 60 -c 20 http://192.168.1.17/index.html
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.17 (be patient).....done


Server Software:        nginx/1.14.1
Server Hostname:        192.168.1.17
Server Port:            80

Document Path:          /index.html
Document Length:        6 bytes

Concurrency Level:      20
Time taken for tests:   0.048 seconds
Complete requests:      10
Failed requests:        6                          # 成功了4个请求(3+1)
   (Connect: 0, Receive: 0, Length: 56, Exceptions: 0)
Write errors:           0
Non-2xx responses:      56
Total transferred:      3250 bytes
HTML transferred:       1302 bytes
Requests per second:    359.27 [#/sec] (mean)      # 吞吐量
....

>>> 可以看到一共10个请求,失败了6个,成功了4个,缓冲区生效了

# 查看 /var/log/nginx/access.log日志,可以发现在1s内,服务器端处理了4个请求(峰值速度:burst+原来的处理速度)。对于剩下的6个请求,直接返回503
[root@mysql-slave ~]$ tail -f /var/log/nginx/access.log 
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 200 6 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"
192.168.1.13 - - [14/Nov/2018:17:49:53 +0800] "GET /index.html HTTP/1.0" 503 213 "-" "ApacheBench/2.3" "-"


# 查看/var/log/nginx/error.log日志,发现有6个请求被直接拒绝了,没有延时请求。
[root@mysql-slave ~]$ tail -f /var/log/nginx/error.log 
2018/11/14 17:49:53 [error] 20735#20735: *478 limiting requests, excess: 3.981 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"
2018/11/14 17:49:53 [error] 20735#20735: *479 limiting requests, excess: 3.980 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"
2018/11/14 17:49:53 [error] 20735#20735: *480 limiting requests, excess: 3.980 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"
2018/11/14 17:49:53 [error] 20735#20735: *481 limiting requests, excess: 3.978 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"
2018/11/14 17:49:53 [error] 20735#20735: *482 limiting requests, excess: 3.978 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"
2018/11/14 17:49:53 [error] 20735#20735: *483 limiting requests, excess: 3.977 by zone "req_zone", client: 192.168.1.13, server: localhost, request: "GET /index.html HTTP/1.0", host: "192.168.1.17"

Nginx请求限制总结

  • limit_req zone=req_zone;
    超过rate处理能力范围的,直接drop
    表现为对收到的请求无延时
  • limit_req zone=req_zone burst=3;
    同时设置了一个大小为3的缓冲队列,在缓冲队列中的请求会等待慢慢处理
    超过了burst缓冲队列长度和rate处理能力的请求被直接丢弃
    表现为对收到的请求有延时
  • limit_req zone=req_zone burst=3 nodelay;
    设置大小为3的缓冲队列,当请求到来时,会爆发出一个峰值处理能力,对于峰值处理数量之外的请求,直接丢弃
    在完成峰值请求之后,缓冲队列不能再放入请求。如果rate=1r/s,且这段时间内没有请求再到来,则每1s 缓冲队列就能回复一个缓冲请求的能力,直到回复到能缓冲3个请求位置。

Nginx连接频率限制配置

参考文档:http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

  • 语法:
# 全局定义连接限制
句法:	limit_conn_zone key zone-name:size;
默认:	-
语境:	http

# 引用连接限制
句法:	limit_conn zone number;
默认:	-
语境:	http,server,location


# 实例配置[root@mysql-slave /etc/nginx]$ vim nginx.conf
...
limit_conn_zone $binary_remote_addr zone=conn_zone:10m;
server {
        listen  80;
        server_name localhost;
        root    /usr/share/nginx/html/www;
        index   index.html;

        location / {
                root    /usr/share/nginx/html/www;
                index   index.html;
                #limit_req zone=req_zone;
                #limit_req zone=req_zone burst=3 nodelay;
                #同一时间只允许一个客户端IP连接
                limit_conn conn_zone 1;
        }
}

 类似资料: