最近收到需求,需要在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作为衡量标准。