Last Updated on

前言

Nginx作为高性能的反向代理服务器,常用于后台web服务,接口服务等等的入口。nginx本身提供了相关的安全限制功能,这里主要来讲一下通过nginx限制访问速率,连接数(并发数),带宽速度等,以防止恶意DDOS,恶意爬虫等攻击导致服务崩溃,还可以通过第三方模块实现对超出限制对IP进行封IP处理。

正文

1. 环境

centos7,nginx-1.12.2,yum安装的nginx。要实现限制,需要使用到nginx的ngx_http_limit_conn_module,ngx_http_limit_req_module模块。

如果是源码安装nginx,这些模快都是nginx安装时自动安装的,不需要额外指定添加模块,直接编译安装即可。

nginx的安装,请参看《centos7 nginx 安装与配置实现负载均衡》

2. 限制连接数

参数说明

limit_conn_zone:在http{}层中配置,定义密钥并设置共享内存区域的参数(工作进程将使用此区域来共享密钥值的计数器)。作为第一个参数,指定作为键计算的表达式。在第二个参数中zone,指定区域的名称及其大小,如下,订一个名为myname的zone,大小10m:

limit_conn_zone $binary_remote_addr zone=myname:10m;

其中 $binary_remote_addr 就是用来作为密钥的字段,是$remote_addr的更小的二进制形式。就是客户端IP的二进制,这个参数非常重要,决定着根据什么来限制连接数,最常用的就是$binary_remote_addr,除此之外,还可以使用$server_name。

limit_conn:指定共享内存区域的名称作为第一个参数,将每个键允许的连接数指定为第二个参数,可以定义在location{} server {} http {}等配置中。

server  { 
    limit_conn myname 1;
}

示例配置

http {
    ...  
    
    limit_conn_zone $binary_remote_addr zone=myname:10m;
    server {
        ...
        location / {
            limit_conn myname 1;
            ...
        }
    }
}

3. 限制连接速率

速率限制可用于防止DDoS攻击,或防止后端服务器同时被太多请求淹没,导致服务崩溃,反应慢等,此方法基于漏桶算法,常见的高并发限流算法之一,其原理详情请看:《高并发限流算法之漏桶算法&令牌桶算法》

参数说明

limit_req_zone:此配置在http{} 层中定义,定义密钥并设置共享内存区域的参数,后跟三个参数,示例如下:

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

跟连接数限制类似,第一个参数为作为密钥的字段,zone为创建的共享内存区域,该区域保持使用$binary_remote_addr变量设置的客户端IP地址的状态,$binary_remote_addrIPv4地址的值大小为4个字节,64位平台上的存储状态占用128个字节。因此,大约16,000个IP地址的状态信息占用该区域的1兆字节。

如果在nginx需要添加新条目时存储空间耗尽,则会删除最旧的条目。如果释放的空间仍然不足以容纳新记录,nginx将返回默认503状态码,可以自定义返回状态码。

rate:每秒请求数(r/s)或每分钟请求数(r/m),指定的请求速率限制,也就是漏桶漏水的速率。

limit_req:类似的,可以在http{},server{},location{}中使用。此配置有三个参数,示例如下:

limit_req zone=one burst=5 nodelay;

# 或者
limit_req zone=one burst=5 delay=3;

zone指定共享内存空间名称,那么就会使用此空间所设置的rate速率作为限制,且相同使用此空间的数据,都可以共享此空间数据。

burst为突发请求队列,也就是漏桶的桶,当请求的速率超过设定的rate后,突发的请求会被放到突发请求的队列中,等待进行处理,以保证速率为设定的rate。因为对于http请求来说,都不会匀速的,是一串一串的,在点开一个页面是瞬间发出多个请求的,所在在短时间内就很容易达到速率上限,此配置就可以很好的解决此问题,但是长时间上,总体速率还是等于设置的rate,也就是漏桶的漏水速率。

nodelay/delay:如何设置为delay=3,那么5个延迟的请求,前面3个将不延迟,直接请求,后2个(burst-delay)则继续延迟请求。这样的话,总的速率限制就变成了rate+delay,相当于加大了漏桶的漏水口,桶的容量就变成burst-delay。如果是nodelay,则是不延迟,直接请求,那么总体速率就会变成rate+burst,也就是没有桶了,纯加大漏水口,超过漏水速率的请求则会直接默认返回503状态码。

limit_req_status: 设置要响应拒绝的请求而返回的状态码,默认503。示例:

limit_req_status 503;

示例配置

http {
    ...  
    
    limit_req_zone $binary_remote_addr zone=myname:10m rate=10r/s;
    server {
        ...
        location / {
            limit_req zone=myname burst=1 nodelay;
            ...
        }
    }
}

如上配置,则谁定速率为一秒10个请求,突发请求多1个,总速率为11/s,超过此速率的请求则会被返回503状态码,但是在时间使用的时候,你会发现,你开发一个页面发出5个请求,只有前两个请求正常返回了,后面都返回503,然后明明你设置的速率因该是11/s,并没有超过。这是什么原有呢,第一次使用nginx限速的朋友最容易碰到这个问题!!网上很多并没有解释,这里着重说明:因为nginx的速率限制是按毫秒为单位的,不是秒!!!

什么意思?就是说你设置的为10r/s,但实际上nginx在执行的时候,是按照ms单位,那么就是100ms一个请求,也就是100ms只能通过一个请求,加上burst的1个,就是两个,然后http请求并不是匀速的,都是瞬间几个请求过来的,在nnginx日志中使用$msec打印时间戳就能看到每个请求的时间。会发现瞬间来的请求时间相差都非常近,有的甚至是相同时间,所以以ms为单位计算,自然就已经超过了设置的rate,所以就直接返回503了。因此,对于http请求的限制来说,burst非常重要,使用burst+nodelay参数,设置速率限制。

4. 限制带宽

限制每个连接的带宽,可以用于限制下载速度,限制流量等。

参数说明

limit_rate: 通过此设置,可以限制每个连接的带宽,但是客户端可以打开多个连接,所以总流量并不会变小,只是可以避免单个连接独占带宽,是下载更加均衡。可以配置在http{},server{},location{}中。

# 限制下载速度为50kb/s
limit_rate 50k;
# 限制下载速度为5m/s
limit_rate 5m;

limit_rate_after:允许客户端快速下载一定数量的数据后,再限制下载其余数据的速率。同样可以配置在http{},server{},location{}中。

# 前500kb速度可以全速下载,之后限速100k/s
limit_rate_after 500k;
limit_rate 100k;

就这两个配置,而如果要限制目标总体的带宽,那么除了限制连接下载的速度外,还要限制连接速,可以与限制连接数一起使用。

示例配置

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 1m;
            limit_rate 50k;
        }
    }
}

5. 封IP

除了上面的限制访问速率外,还可以通过第三方模块,简单方便的实现对超过访问速率的IP封一段时间,再解封的功能,这需要第三方模块:ngx_dynamic_limit_req_module。此模块是基于ngx_http_limit_req_module模块上,使用方法类似,使用redis作为缓冲,存放IP数据。Github地址:

https://github.com/limithit/ngx_dynamic_limit_req_module

下载此插件,添加到nginx中,nginx添加第三方模块,请参考:

《Centos7 nginx 不中断服务添加新模块或第三方模块》

参数说明:

dynamic_limit_req_zone: 类似limit_req_zone,配置格式如下:
dynamic_limit_req_zone key zone=name:size rate=rate redis=127.0.0.1 block_second=time;

新增配置redis和block_second,redis指定需要用到的redis连接地址,block_second为IP封禁时间。PS:这里配置只有redis host,没有端口,密码,库的设置,这些默认是使用6379端口,没有密码,默认使用0和1两个redis库。而且这些默认是写死在代码里的,如果要使用此第三方模块,最好本机单独起动一个redis服务用于此功能,不要跟其他混合使用。

这些配置的缺失是的不是那么灵活,此模块是c语言写的,有能力的朋友可以去github上修改去代码添加这些配置以方便使用,也或者提Issue看作者会不会再继续完善。

dynamic_limit_req: 类似limit_req,使用方法完全一样,参数也一样。

dynamic_limit_req_status: 与limit_req_status类似,使用方法完全一样。

示例配置

http {
    ...  
    
    dynamic_limit_req_zone $binary_remote_addr zone=aaa:10m rate=10r/s redis=127.0.0.1 block_second=300;
    server {
        ...
        location / {
            dynamic_limit_req zone=aaa burst=40 nodelay;
            ...
        }
    }
}

由于其是基于ngx_http_limit_req_module模块的,所以里面的配置和使用方式都是类似与limit_req_module模块,基本都是一样的,这是多添加了一点配置而已,这样就实现了封禁IP,被封的IP会存在redis的0库中,过期时间为设置的封禁时间,时间到后就解封。

这在防爬中使用的非常常见,添加爬虫的成本以减少被爬导致后端服务崩溃。

结束

OK,到此记录介绍完了,使用这次配置,就可以实现nginx反向代理中的限制连接数,访问速率,带宽,封IP等。灵活使用,以实现需求。

有什么问题,欢迎留言