nginx限流配置

作者: 老鼠AI大米_Java全栈 | 来源:发表于2023-09-13 15:50 被阅读0次

    限流(Rate Limitting)是服务降级的一种方式,通过限制系统的输入和输出流量以达到保护系统的目的。比如我们的网站暴露在公网环境中,除了用户的正常访问,网络爬虫、恶意攻击或者大促等突发流量都可能都会对系统造成压力,如果这种压力超出了服务器的处理能力,会造成响应过慢甚至系统崩溃的问题。因此,当并发请求数过大时,我们通过限制一部分请求(比如限制同一IP的频繁请求)来保证服务器可以正确响应另一部分的请求。

    Nginx的limit模块主要包括: ngx_http_limit_req_modulengx_http_limit_conn_modulengx_stream_limit_conn_module以及ngx_http_core_module中limit_rate选项,由于stream主要用来实现四层协议(网络层和传输层)的转发、代理、负载均衡等,并且ngx_stream_limit_conn_modulengx_http_limit_conn_module配置基本相同

    限制请求速率ngx_http_limit_req_module

    req_module提供限制请求处理速率的能力,使用了漏桶算法。我们可以想像有一只上面进水、下面匀速出水的桶,如果桶里面有水,那刚进去的水就要存在桶里等下面的水流完之后才会流出,如果进水的速度大于水流出的速度,桶里的水就会满,这时水就不会进到桶里,而是直接从桶的上面溢出。对应到处理网络请求,水代表从客户端来的请求,而桶代表一个队列,请求在该队列中依据先进先出(FIFO)算法等待被处理。漏的水代表请求离开缓冲区并被服务器处理,溢出代表了请求被丢弃并且永不被服务。


    image.png

    正常限流

    该模块用来限制某个特定键的请求处理的速率,这个特定键一般为某个ip。该模块主要用到了"漏桶"算法。有关漏桶算法可以点击此处详细查看。

    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    
    server {
       location /search/ {
           limit_req zone=one burst=5 [nodelay|delay=num];
       }
    

    首先使用limit_req_zone定义一个内存区域(定义在http段中):

    $binary_remote_addr: 特定用于该模块的变量,用来存取客户端ip地址。
    zone=one:10m:定义一个大小为10m的共享内存区域,名称为one。对于ipv4地址占用4个字节,ipv6占用16个字节。存储一个连接状态在32位机器上占用64个字节,在64位机器上占用128个字节。1MB的空间可以存储16k个64字节状态或者是8k个128字节状态。所以10m的空间可以存储大约8w个状态,对于一般的中小型站点已经足够了。
    rate=1r/s:定义请求速率为每秒仅接受一个请求
    在server配置段中引用上述配置(第5行):
    zone=one:引用上面limit_req_zone定义的内存区域one
    burst:定义请求缓存的长度,意思是如果某ip1秒内发来了10个请求,那么除了正在处理的1个请求,其他的请求会把暂时放置到burst中排队。超过rate+burst请求将会被全部丢弃
    nodelay:一次处理burst+rate个请求,其余全部丢弃。如果不希望在请求受到限制时延迟过多的请求应当使用这个参数
    delay=num:瞬时处理num+rate个请求,总共缓存burst个,剩余的burst-num按rate定义处理。丢弃多余请求。

    其他指令

    • limit_req_dry_run
    语法:   limit_req_dry_run on | off
    默认值: limit_req_dry_run off;
    语境:   http server location
    

    该指令开启"干跑"模式。开启该配置,请求处理的速率不再被限制。但是在共享内存区域,过量的请求的数值会像之前一样计算。

    • limit_req_log_level
    语法:   limit_req_log_level info | notice | warn | error;
    默认值: limit_req_log_level error;
    语境:   http server location
    

    该指令设置rate超过限值或者延时请求处理的日志级别,默认为error级别。

    • limit_req_status
    语法:   limit_req_status code;
    默认值:  limit_req_status 503;
    语境:    http server location
    

    该指令定义拒绝响应请求的http状态码,默认返回*503

    测试

    1、不开启burst,不开启nodelay

    配置如下所示:

     http {
        include       mime.types;
        default_type  application/octet-stream;
    
        limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    
        log_format  main  '$remote_addr "$request"'
                          '$status'
                          '"$http_user_agent"';
    
        server {
            listen       80;
            server_name  localhost;
    
            charset utf-8;
    
            location / {
                limit_req zone=one;
                root   /usr/share/nginx/html/;
                index  index.html index.htm;
            }
        }
    }
    

    在另一台虚拟机使用Apahce Benchmark进行压力测试

    ab -c 10 -n10 http://192.168.0.106/index.html
    

    结果如下:

    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    

    严格按照rate定义处理请求,除了第一个请求外其余所有的请求全部丢弃并返回503。

    2、在18行末尾添加burst=5

    192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:36 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:37 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:38 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:39 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:53:40 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    

    按照定义,1秒内来了10个请求,burst=5,rate=1。总共缓存5个请求,处理1个请求,其余全部丢弃。这就是21:53:35内丢弃了4个请求。随后缓存的请求按照rate定义值处理。

    3、在18行末尾继续添加nodelay

    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    

    瞬间提供rate+burst个请求处理能力,丢弃其他请求返回503。

    4、将18行的nodelay改成delay=3

    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:06 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:42:07 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
    

    可以看出nginx瞬间处理了4(rate+delay)个请求,缓存了2个请求,其余请求返回503。缓存下来的请求每秒处理一个。

    限制连接数量

    conn_module与ngx_http_limit_req_module主要区别在于,conn限制的连接的数量,req限制的是请求的数量。一个连接的生命周期中,会存在一个或者多个请求,这是为了加快效率,避免每次请求都要三次握手建立连接,现在的HTTP/1.1协议都支持这种特性,叫做keepalive。

    官方配置示例:

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

    limit_conn_zone:设置共享区域的参数,该区域将保留各种键的状态,与ngx_http_limit_req_module不同的是,此模块仅定义了区域了大小。键里可以保存文本,变量或者两者的组合。

    limit_conn:设置共享内存区域以及键定义的最大允许的连接数量。超过限值时,服务器将会发送错误给客户端。limit_conn addr 1允许一个ip一次最多建立一次连接。如果是HTTP/2,那么每个并发的请求都会被视作是一个单独的连接。

    limit_conn_dry_run,limit_conn_log_level,limit_conn_status与ngx_http_limit_req_module中一致不再说明。

    测试

    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
    192.168.0.107 - - [05/May/2020:23:54:29 +0800] "GET /test.html HTTP/1.0" 200 536870912 "-" "ApacheBench/2.3"
    

    test.html为50M,测试1秒内仅能建立一个连接其余全部丢弃。但index.html测试失败,不知道和文件的大小存在什么关联?

    limit_rate

    limit_rate是ngx_http_core_module这个核心模块自带的一个配置选项。可以用来限制单个连接的下载速率。对于资源文件下载服务器来说,有必要限制这个值,防止单个ip过高的速率影响他人的正常使用。

    语法:    limit_rate rate;
    默认值:  limit_rate 0;
    语境:    http, server, location, if in location
    

    默认情况下,这个值是0,也就是不限制下载速率。官方推荐使用map来灵活定义下载速率。如:

    map $slow $rate {
        0     40k;
        1     80k;
        default 120k;
    }
    limit_rate $rate; #http,server,location,if in location
    

    当然slow不是nginx自带的变量,所以直接使用会报错。需要根据ngx_http_geo_module模块自带的geo指令来定义slow变量。

    geo $remote_addr $slow {
        default 0;
        47.103.215.250 1;
    }
    

    以上是这样的过程:remote_addr是nginx自带的变量,geo指令拿到这个变量对应的ip地址去和下面的default或47.103.215.250匹配,如果满足remote_addr=47.103.215.250,则把slow变量设置为1,否则就设置为0。再拿0或者1去map里匹配得到rate,最后在limit_rate中应用$rate来达到限制速率的要求。

    注:geo 和 map都是应用在http段中

    image.png
    本地下载测试为40KB/s
    image.png
    47.103.215.250上测试为80KB/s

    当然limit_rate只能限制单个ip的一个请求:

    如果客户端同时发起了两个链接,这个下载速度会变成限值的2倍。所以对于迅雷这种多线程的下载器,设置这样的限值的无效的。

    解决的方法就是配合ngx_http_limit_conn_module模块,让服务器每秒只响应一个ip的一个请求,其余请求全部丢弃。

        limit_conn_zone $binary_remote_addr zone=one:10m;
        server {
            listen       80;
            server_name  localhost;
            
            location / {
                limit_conn one 1;
                limit_rate 80k;
                root   /usr/share/nginx/html/;
            }
    
        }
    

    在测试端下载test.html

    [root@k8s-node ~]# wget http://192.168.0.106/test.html
    --2020-05-06 00:00:43--  http://192.168.0.106/test.html
    Connecting to 192.168.0.106:80... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 536870912 (512M) [text/html]
    Saving to: ‘test.html.2’
    
    test.html.2                        
    0%[                                                    ]   1.29M  79.6KB/s    eta 1h 48m 
    

    复制终端会话再次请求:

    Last login: Tue May  5 23:40:24 2020 from 192.168.0.101
    [root@k8s-node ~]# wget http://192.168.0.106/test.html
    --2020-05-06 00:01:18--  http://192.168.0.106/test.html
    Connecting to 192.168.0.106:80... connected.
    HTTP request sent, awaiting response... 503 Service Temporarily Unavailable
    2020-05-06 00:01:19 ERROR 503: Service Temporarily Unavailable.
    

    nginx直接返回了503,这样就达到了限制单个ip的请求速率的目的。

    参考:
    https://blog.csdn.net/qq_35760825/article/details/127596936
    https://www.cnblogs.com/ltzhang/p/13544562.html

    相关文章

      网友评论

        本文标题:nginx限流配置

        本文链接:https://www.haomeiwen.com/subject/zorpvdtx.html