The resty.limit.traffic module allows you to aggregate multiple different limiter instances (such as request rate limiters and connection concurrency limiters) into a single check. This is useful when you want to enforce multiple constraints simultaneously (e.g., limiting by both Host IP and Client IP) without adding significant CPU overhead.
How it works
- Aggregation: You provide an array of limiter objects and a corresponding array of keys.
- Delay Calculation: The module calculates the maximum delay required across all limiters. The caller is responsible for calling
ngx.sleep(delay). - Atomic Rejection: If any single limiter in the set rejects the request,
combine ensures the request is not committed in any of the other limiters in the set. It returns nil and the error string "rejected". - State Tracking: You can optionally provide a
states table to capture metadata from the limiters (e.g., current concurrency levels or excess request rates).
Requirements for Limiter Objects
To be compatible with combine, a limiter object must implement:
incoming(key, commit): Returns delay, state_or_error. If delay is nil, the second return value is an error string. If delay is not nil, the second value is an opaque state value.uncommit(key): Used to undo a committed incoming call if the overall combination fails or needs adjustment.
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"
-- Initialize limiters
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
local keys = {host, client, client}
local states = {}
-- Combine limiters
local delay, err = limit_traffic.combine(limiters, keys, states)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit traffic: ", err)
return ngx.exit(500)
end
-- Handle connection limiting cleanup in log_by_lua
if lim3:is_committed() then
local ctx = ngx.ctx
ctx.limit_conn = lim3
ctx.limit_conn_key = keys[3]
end
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
local latency = tonumber(ngx.var.request_time)
local key = ctx.limit_conn_key
assert(key)
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
}
}
}
}