序言
最近收到需求,需要在openresty上進行二次開發,對四層/七層負載進行限流以及限速。針對此,收集相關資料如下所示:
http限流模塊:
- ngx_http_limit_conn_module;
- ngx_http_limit_req_module;
- lua-resty-limit-traffic;
stream限流模塊:
- ngx_stream_limit_conn_module;
ngx_http_limit_conn_module
ngx_http_limit_conn_modul基於key($binary_remote_addr或者server_name),對網絡總連接數進行限流。
不是所有的網絡連接都會被計數器統計,只有被Nginx處理的並且已經讀取了整個請求頭的連接才會被技術器統計。
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
官網,以下對關鍵配置項進行解釋:
- 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作為衡量標准。