最近收到需求,須要在openresty上進行二次開發,對四層/七層負載進行限流以及限速。針對此,收集相關資料以下所示:html
http限流模塊:node
stream限流模塊:nginx
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
官網,如下對關鍵配置項進行解釋:算法
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
執行流程:緩存
(1)請求進入後判斷最後一次請求時間相對於當前時間是否須要進行限流,若不須要,執行正常流程;不然,進入步驟2;
(2)若是未配置漏桶容量(默認0),按照固定速率處理請求,若此時請求被限流,則直接返回503;不然,根據是否配置了延遲默認分別進入步驟3或步驟4;
(3)配置了漏桶容量以及延遲模式(未配置nodelay),若漏桶滿了,則新的請求將被限;若是漏桶沒有滿,則新的請求會以固定平均速率被處理(請求被延遲處理,基於sleep實現);
(4)配置了漏桶容量以及nodelay,新請求進入漏桶會便可處理(不進行休眠,以便處理固定數量的突發流量);若漏桶滿了,則請求被限流,直接返回響應。網絡
使用場景比較固定的狀況下,推薦使用自有模塊。
需求比較複雜時(產品要求動態化設置時),建議採用lua-resty-limit-traffic實現限流請求(較新的openresty已自動包含此庫,無需手動引入)。
lua-resty-limit-traffic模塊主要由如下四個子模塊構成:
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發行版中(無需手動安裝),被大範圍使用,具備較高的穩定性。
此模塊在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; } } }
限速配置,詳見註釋內容。
upstream backend { server backend1.example.com weight=5 max_conns=5; }
在upstream模塊中,能夠針對server設置max_conns參數。
此參數的實際做用是,限制同一時間段內,鏈接到後端的最大鏈接數,以保證後端服務不被突發的鏈接沖垮。
衡量系統響應時間,由於request_time受客戶端鏈接質量影響較大,應該使用upstream_response_time做爲衡量標準。