Nginx限流以及限速配置

序言

最近收到需求,須要在openresty上進行二次開發,對四層/七層負載進行限流以及限速。針對此,收集相關資料以下所示:html

http限流模塊:node

  • ngx_http_limit_conn_module;
  • ngx_http_limit_req_module;
  • lua-resty-limit-traffic;

stream限流模塊:nginx

  • ngx_stream_limit_conn_module;

ngx_http_limit_conn_module

ngx_http_limit_conn_modul基於key($binary_remote_addr或者server_name),對網絡總鏈接數進行限流。git

不是全部的網絡鏈接都會被計數器統計,只有被Nginx處理的而且已經讀取了整個請求頭的鏈接纔會被技術器統計。github

http{
    # 針對客戶端地址,進行鏈接數限制
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    # 針對域名,進行鏈接數限制
    limit_conn_zone $server_name zone=perserver:10m;
    limit_conn_log_level error;
    limit_conn_status 503;

    server {
        # 每一個IP僅容許發起10個鏈接
        limit_conn perip 10;
        # 每一個域名僅容許發起100個鏈接
        limit_conn perserver 100;
    }
}

以上配置,節選自ngx_http_limit_conn_module官網,如下對關鍵配置項進行解釋:算法

  • limit_conn_zone: 用於配置限流key及存放限流key對應的共享內存大小;
  • limit_conn_log_level: 請求被限流後的日誌級別,默認error級別;
  • limit_conn_status:請求被限流後返回的http狀態碼,默認503;
  • limit_conn:配置存放key的共享內存區域名稱和指定key的最大鏈接數;

ngx_http_limit_req_module

ngx_http_limit_req_module基於key(基本上爲客戶端IP地址)對請求進行限流(基於漏桶算法)。後端

http{
    # 固定請求速率爲1個請求/每秒
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    limit_req_log_level error;
    limit_req_status 503;

    server {
        location /search/ {
            # 漏桶容量爲5
            limit_req zone=one burst=5 nodelay;
        }
    }  
}

以上配置,節選自ngx_http_limit_req_module官網,如下對關鍵配置項進行解釋:api

  • limit_req_zone:配置限流key,存放key的共享內存區域大小,以及固定請求速率;
  • limit_req_log_level: 參照limit_conn_log_level;
  • limit_req_status:參照limit_conn_status;
  • limit_req: 配置限流區域,漏桶容量(突發容量,默認爲0),是否採用延遲模式(默認延遲);

執行流程:緩存

(1)請求進入後判斷最後一次請求時間相對於當前時間是否須要進行限流,若不須要,執行正常流程;不然,進入步驟2;
(2)若是未配置漏桶容量(默認0),按照固定速率處理請求,若此時請求被限流,則直接返回503;不然,根據是否配置了延遲默認分別進入步驟3或步驟4;
(3)配置了漏桶容量以及延遲模式(未配置nodelay),若漏桶滿了,則新的請求將被限;若是漏桶沒有滿,則新的請求會以固定平均速率被處理(請求被延遲處理,基於sleep實現);
(4)配置了漏桶容量以及nodelay,新請求進入漏桶會便可處理(不進行休眠,以便處理固定數量的突發流量);若漏桶滿了,則請求被限流,直接返回響應。網絡

lua-resty-limit-traffic

使用場景比較固定的狀況下,推薦使用自有模塊。
需求比較複雜時(產品要求動態化設置時),建議採用lua-resty-limit-traffic實現限流請求(較新的openresty已自動包含此庫,無需手動引入)。

lua-resty-limit-traffic模塊主要由如下四個子模塊構成:

  • resty.limit.req:基於漏桶算法對請求進行限流操做;
  • resty.limit.count:基於固定窗口實現流量控制;
  • resty.limit.conn:實現請求限流以及流量整形;
  • resty.limit.traffic:提供聚合器,以便整合resty.limit.req、resty.limit.count以及resty.limit.conn。
http {
    lua_shared_dict my_req_store 100m;
    lua_shared_dict my_conn_store 100m;

    server {
        location / {
            access_by_lua_block {
                local limit_conn = require "resty.limit.conn"
                local limit_req = require "resty.limit.req"
                local limit_traffic = require "resty.limit.traffic"

                -- 建立請求限流器,參數分別爲共享內存名稱,執行速率(設置爲300個請求/每秒),漏桶容量
                local lim1, err = limit_req.new("my_req_store", 300, 200)
                assert(lim1, err)
                -- 建立請求限流器
                local lim2, err = limit_req.new("my_req_store", 200, 100)
                assert(lim2, err)
                -- 建立鏈接限流器,參數分別爲共享內存名稱,最大鏈接數(超過最大鏈接數,小於最大鏈接數+漏桶容量的鏈接將被延遲處理),漏桶容量,初始鏈接延遲時間
                local lim3, err = limit_conn.new("my_conn_store", 1000, 1000, 0.5)
                assert(lim3, err)

                local limiters = {lim1, lim2, lim3}

                local host = ngx.var.host
                local client = ngx.var.binary_remote_addr
                -- 傳入限流器所使用的key,lim1基於請求中的‘Host’,lim2基於客戶端地址,lim3基於客戶端地址
                local keys = {host, client, client}

                local states = {}

                -- 聚合限流器,完成複合功能
                local delay, err = limit_traffic.combine(limiters, keys, states)
                if not delay then
                    if err == "rejected" then
                        -- 請求被限流,返回503
                        return ngx.exit(503)
                    end
                    -- 限流器內部執行存在錯誤,返回500
                    ngx.log(ngx.ERR, "failed to limit traffic: ", err)
                    return ngx.exit(500)
                end

                if lim3:is_committed() then
                    -- 當前請求未被限流,且鏈接被記錄到共享內存中(能夠不記錄,採用dry run方式)
                    local ctx = ngx.ctx
                    ctx.limit_conn = lim3
                    ctx.limit_conn_key = keys[3]
                end

                print("sleeping ", delay, " sec, states: ",
                      table.concat(states, ", "))

                if delay >= 0.001 then
                    -- 超過漏桶容量,未被限流的請求,須要休眠一段時間以保證請求勻速執行
                    ngx.sleep(delay)    
                end
            }

            log_by_lua_block {
                local ctx = ngx.ctx
                local lim = ctx.limit_conn
                if lim then
                    -- if you are using an upstream module in the content phase,
                    -- then you probably want to use $upstream_response_time
                    -- instead of $request_time below.
                    -- 記錄http請求的響應時間,若是有upstream module,那麼建議使用$upstream_response_time代替$request_time
                    local latency = tonumber(ngx.var.request_time)
                    local key = ctx.limit_conn_key
                    assert(key)
                    -- 根據當前http請求的執行時間,更新請求的延遲時間 
                    local conn, err = lim:leaving(key, latency)
                    if not conn then
                        ngx.log(ngx.ERR,
                                "failed to record the connection leaving ",
                                "request: ", err)
                        return
                    end
                end
            }
        }
    }
}

以上配置,節選自lua-resty-limit-traffic代碼倉庫。由於配置項較多,所以部分配置,在配置項經過註釋進行解釋。

由以上配置可知,lua-resty-limit-traffic的配置能夠進行組合,此外key的指定也比較靈活。此外lua-resty-limit-traffic模塊已經被正式包含在openresty發行版中(無需手動安裝),被大範圍使用,具備較高的穩定性。

ngx_stream_limit_conn_module

此模塊在TCP/UDP會話的Pre-access階段被處理。

stream {
    # 客戶端地址限流共享內存區域
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    server {
        # 限制每一個客戶端僅能發起一個鏈接 
        limit_conn           addr 1;
        # 限流記錄日誌級別
        limit_conn_log_level error;
    }
}

Nginx對於stream模塊的支持度,現階段還不夠高。限流模塊,僅能針對客戶端地址進行鏈接限流(stream模塊中,無$server_name屬性)。

限速配置

stream {
    upstream site {
        server your.upload-api.domain1:8080;
        server your.upload-api.domain1:8080;
    }

    server {
        listen    12345;
        proxy_pass site;

        # 19 MiB/min = ~332k/s,限制上行和下行的速率均爲19MiB/min
        proxy_upload_rate 332k;
        proxy_download_rate 332k;
    }
}

http {

  server {
    location = /upload {
        
        # 關閉請求緩存,請求直接代理至後端
        proxy_request_buffering off;    
        
        # It will pass to the stream
        # Then the stream passes to your.api.domain1:8080/upload?$args
        proxy_pass http://127.0.0.1:12345/upload?$args;
   
    }

    location /download {
        # 下載大文件,須要調整超時時間/keepalive至較大數值
        keepalive_timeout  28800s;
        proxy_read_timeout 28800s;  
        proxy_buffering on;

        # 75MiB/min = ~1300kilobytes/s
        # 限制Nginx讀取後端響應速率(須要開啓代理緩存才能生效)
        # proxy_limit_rate 1300k; 
        
        # 下載500k以後,進行限速(適用於小文件下載)
        limit_rate_after 500k;
        # 限制下載速度爲50k/每秒
        limit_rate       50k;

        proxy_pass your.api.domain1:8080;
    }
  }
}

限速配置,詳見註釋內容。

補充知識

max_conns:

upstream backend {
    server backend1.example.com weight=5 max_conns=5;
}

在upstream模塊中,能夠針對server設置max_conns參數。
此參數的實際做用是,限制同一時間段內,鏈接到後端的最大鏈接數,以保證後端服務不被突發的鏈接沖垮。

request_time與upstream_response_time

  • request_time:Nginx接收客戶端請求,到發送完響應的時間段;
  • upstream_response_time:Nginx創建到後端的鏈接,到接收完代理後端響應的時間;

衡量系統響應時間,由於request_time受客戶端鏈接質量影響較大,應該使用upstream_response_time做爲衡量標準。

相關文章
相關標籤/搜索