基于OpenResty实现动态限流:按QPS、时间区间与来源IP
在OpenResty环境中,通过结合Nginx原生模块与Lua脚本能力,可灵活实现多维度流量控制。本文聚焦于如何依据每秒请求量(QPS)、特定时间段及客户端IP地址进行精细化限流,并支持策略的实时更新,无需重启服务。
一、多条件限流策略实现
1. 基于QPS的限流
利用ngx_http_limit_req_module模块,配合漏桶算法对单个客户端请求频率进行限制。配置示例如下:
http {
limit_req_zone $binary_remote_addr zone=api_rate:10m rate=5r/s;
server {
location /api {
limit_req zone=api_rate burst=10 nodelay;
# 允许突发10次请求,不延迟处理
}
}
}
此配置将每个客户端的平均请求速率限制为每秒5次,允许短时间内瞬时爆发10次请求。
2. 按时间范围限流
若需在特定时段(如工作日9:00–18:00)启用更严格限流,可通过Lua脚本判断当前时间并动态调整策略:
access_by_lua_block {
local now = os.date("%H")
local is_business_hour = (now >= "09" and now <= "18")
if not is_business_hour then
return -- 非业务时段不限流
end
local lim = require "resty.limit.req"
local limit, err = lim.new("req_store", 3, 0.1)
if not limit then
ngx.log(ngx.ERR, "创建限流器失败: ", err)
return ngx.exit(500)
end
local key = ngx.var.binary_remote_addr
local delay, err = limit:incoming(key, true)
if delay > 0.001 then
ngx.sleep(delay) -- 调整请求节奏
elseif err == "rejected" then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.header.content_type = "application/json"
ngx.say([[{"code":429,"msg":"请求过于频繁,请稍后再试"}]])
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
}
该逻辑仅在工作时间内实施限流,使用resty.limit.req库实现精准控制。
3. 按来源IP连接数限制
通过limit_conn_zone模块限制每个客户端的并发连接数,防止资源耗尽:
http {
limit_conn_zone $binary_remote_addr zone=ip_conn:10m;
server {
location / {
limit_conn ip_conn 5; -- 每个IP最多5个并发连接
}
}
}
二、限流响应与状态码定制
当触发限流规则时,应返回清晰且友好的错误信息。对于API接口,推荐使用标准状态码和结构化响应:
if should_be_blocked then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.header["Content-Type"] = "application/json"
ngx.say([[{"error":"rate_limited","details":"请求频率超限,建议间隔重试"}]])
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
相比返回固定503页面,这种做法更具可读性和兼容性,尤其适用于微服务架构中的前端或客户端。
三、动态更新限流规则而不重启服务
为实现策略实时生效,可采用以下机制:
1. 使用共享字典(shared dict)
借助OpenResty提供的内存共享存储,将限流参数动态写入并读取:
http {
lua_shared_dict req_store 10m;
init_by_lua_block {
ngx.shared.req_store:set("max_qps", 3)
}
server {
location /api {
access_by_lua_block {
local max_qps = ngx.shared.req_store:get("max_qps") or 5
local lim = require "resty.limit.req".new("req_store", max_qps, 0.5)
local key = ngx.var.binary_remote_addr
local delay = lim:incoming(key, true)
if delay > 0.001 then
ngx.sleep(delay)
elseif delay == nil then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say("{\"error\":\"too_many_requests\"}")
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
}
}
}
}
修改max_qps值后,新请求立即生效,无需重载配置。
2. 外部配置中心集成
通过调用外部服务获取最新限流参数,实现集中管理:
access_by_lua_block {
local http = require "resty.http"
local client = http.new()
local res, err = client:request_uri("http://config-service/rate-limit/api", {
method = "GET",
headers = { ["Accept"] = "application/json" }
})
if not res or res.status ~= 200 then
ngx.log(ngx.WARN, "获取限流配置失败: ", err)
return -- 降级处理
end
local config = cjson.decode(res.body)
local rate = config.max_qps or 5
local lim = require "resty.limit.req".new("req_store", rate, 0.2)
local key = ngx.var.binary_remote_addr
local delay = lim:incoming(key, true)
if delay > 0.001 then
ngx.sleep(delay)
elseif delay == nil then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say(cjson.encode({error="rate_limited"}))
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
}
结合etcd、Consul或自建配置中心,可实现跨实例统一策略同步。
3. 信号热加载(reload)
虽然非实时,但可通过nginx -s reload命令重新加载配置文件,适用于配置变更频率较低的场景。
总结
在OpenResty中,通过组合使用内置模块、Lua脚本与共享字典机制,可以构建出高灵活性、高可用性的动态限流系统。其核心优势在于:无需重启服务即可更新规则,支持按时间、来源、速率等复杂条件精确控制流量,是保障后端服务稳定性的关键手段。