Nginx + Lua动态限流与AB分流:香港服务器流量精细化管理完整方案
Nginx 原生的 limit_req 限流模块固定在配置文件里,调整规则必须 reload;无法根据用户等级设置不同限流阈值;也无法实现基于业务逻辑的 AB 分流。OpenResty(Nginx + LuaJIT)将 Lua 脚本嵌入 Nginx 请求处理流程,配合 Redis 实现完全动态、可实时调整、业务感知的流量管理,是香港服务器高并发架构的进阶方案。
一、OpenResty 安装
<code"># Ubuntu 22.04 安装 OpenResty(自带 LuaJIT 和所有 Lua 模块)
apt install -y apt-transport-https gnupg
wget -qO - https://openresty.org/package/pubkey.gpg | apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/openresty.list
apt update && apt install -y openresty
# 验证
openresty -v
openresty -e 'ngx.say("Hello Lua")'
# OpenResty 使用 /usr/local/openresty/ 作为根目录
# 主配置文件:/usr/local/openresty/nginx/conf/nginx.conf二、基于 Redis 的动态限流(令牌桶算法)
<code"># /usr/local/openresty/nginx/conf/nginx.conf
http {
# 共享内存(Lua 模块间共享数据)
lua_shared_dict limit_cache 10m;
lua_shared_dict api_tokens 5m;
# Redis 连接池(复用连接,避免频繁建连)
upstream redis_pool {
server 127.0.0.1:6379;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
location /api/ {
access_by_lua_block {
local redis = require "resty.redis"
local cjson = require "cjson"
-- 获取请求标识(优先用 API Key,其次用 IP)
local api_key = ngx.req.get_headers()["X-API-Key"] or ngx.var.remote_addr
-- 从 Redis 获取该 Key 的限流配置(支持按 Key 动态设置不同阈值)
local red = redis:new()
red:set_timeout(100) -- 100ms 超时
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis 连接失败: ", err)
-- Redis 故障时放行(Fail Open 策略,可改为 Fail Closed)
return
end
-- 获取该 Key 的每分钟限额(未设置则使用默认值100)
local limit_key = "rate_limit:" .. api_key
local current_limit = red:get(limit_key) or 100
current_limit = tonumber(current_limit)
-- 使用 Redis 滑动窗口计数
local window_key = "req_count:" .. api_key .. ":" .. math.floor(ngx.now() / 60)
local count = red:incr(window_key)
if count == 1 then
red:expire(window_key, 120) -- 2分钟后过期
end
-- 关闭连接(归还连接池)
red:set_keepalive(10000, 32)
-- 超出限额时拒绝
if count > current_limit then
ngx.status = 429 -- Too Many Requests
ngx.header["Content-Type"] = "application/json"
ngx.header["X-RateLimit-Limit"] = current_limit
ngx.header["X-RateLimit-Remaining"] = 0
ngx.header["Retry-After"] = 60
ngx.say(cjson.encode({
error = "rate_limit_exceeded",
message = "请求频率超限,请稍后重试",
retry_after = 60
}))
ngx.exit(ngx.HTTP_OK) -- 返回429但不触发 Nginx 错误页
end
-- 设置限流信息头(方便客户端了解剩余配额)
ngx.header["X-RateLimit-Limit"] = current_limit
ngx.header["X-RateLimit-Remaining"] = math.max(0, current_limit - count)
}
proxy_pass http://backend_app;
}
}
}三、动态调整限流规则(无需 reload)
<code"># 管理接口:实时修改某个 API Key 的限流阈值
# 通过 Redis CLI 直接修改,立即生效,无需重启 Nginx
# 为 VIP 用户提升限额(每分钟1000次)
redis-cli SET "rate_limit:vip_api_key_123" 1000 EX 86400
# 为免费用户降低限额(每分钟10次)
redis-cli SET "rate_limit:free_user_456" 10
# 临时封禁某个 IP(设置为-1,Lua 脚本中检查)
redis-cli SET "rate_limit:1.2.3.4" -1 EX 3600
# 也可以写 Python 脚本批量管理
import redis
r = redis.Redis()
# 批量设置用户限额(从数据库读取用户等级)
tier_limits = {"free": 60, "pro": 600, "enterprise": 6000}
for api_key, tier in user_tiers.items():
r.set(f"rate_limit:{api_key}", tier_limits[tier], ex=86400)四、基于用户属性的 AB 流量分割
<code"># 基于用户 ID 尾数分流(稳定分流,同一用户始终进同一组)
location /api/checkout {
rewrite_by_lua_block {
local user_id = ngx.req.get_headers()["X-User-ID"]
if not user_id then
-- 未登录用户默认走稳定版
ngx.var.backend_target = "checkout_stable"
return
end
-- 取用户 ID 最后一位数字判断分组
local last_digit = tonumber(user_id:sub(-1)) or 0
if last_digit < 2 then
-- 20% 的用户进入实验组(新版结账流程)
ngx.var.backend_target = "checkout_experiment"
ngx.header["X-Experiment"] = "checkout_v2"
else
ngx.var.backend_target = "checkout_stable"
end
}
proxy_pass http://$backend_target;
}
# 定义分流目标
upstream checkout_stable {
server 10.0.0.1:8000;
}
upstream checkout_experiment {
server 10.0.0.2:8001; # 新版结账服务
}五、边缘 JWT 鉴权(不回源验证)
<code"># 在 Nginx 层验证 JWT,合法请求才转发到后端(节省后端资源)
location /api/protected/ {
access_by_lua_block {
local jwt = require "resty.jwt"
-- 从 Authorization Header 提取 Token
local auth_header = ngx.req.get_headers()["Authorization"]
if not auth_header or not auth_header:find("Bearer ") then
ngx.status = 401
ngx.say('{"error":"missing_token"}')
ngx.exit(ngx.HTTP_OK)
end
local token = auth_header:sub(8) -- 去掉 "Bearer "
-- 验证 JWT 签名(无需回源后端)
local jwt_obj = jwt:verify("your_jwt_secret_key", token)
if not jwt_obj.verified then
ngx.status = 401
ngx.say('{"error":"invalid_token","reason":"' .. (jwt_obj.reason or "unknown") .. '"}')
ngx.exit(ngx.HTTP_OK)
end
-- 将用户信息通过 Header 传递给后端
local payload = jwt_obj.payload
ngx.req.set_header("X-User-ID", payload.sub or "")
ngx.req.set_header("X-User-Role", payload.role or "user")
ngx.req.set_header("X-User-Email", payload.email or "")
}
proxy_pass http://backend_app;
}六、请求改写与响应修改
<code"># 根据请求特征动态修改请求参数
location /api/search {
rewrite_by_lua_block {
-- 从 URL 参数中提取用户语言
local args = ngx.req.get_uri_args()
local lang = args["lang"] or ngx.req.get_headers()["Accept-Language"] or "en"
-- 根据语言设置搜索区域(提升搜索精度)
if lang:find("^zh") then
ngx.req.set_uri_args("region=cn&lang=zh&" .. ngx.var.args)
elseif lang:find("^ja") then
ngx.req.set_uri_args("region=jp&lang=ja&" .. ngx.var.args)
end
}
# 压缩响应体中的特定字段(减少带宽)
body_filter_by_lua_block {
-- 可以在此处理响应体(如删除敏感字段)
}
proxy_pass http://search_backend;
}七、性能监控端点
<code"># 暴露 Lua 运行时统计信息
location /metrics/lua {
allow 127.0.0.1;
deny all;
content_by_lua_block {
local limit_cache = ngx.shared.limit_cache
local api_tokens = ngx.shared.api_tokens
ngx.header["Content-Type"] = "application/json"
ngx.say(require("cjson").encode({
limit_cache_capacity = limit_cache:capacity(),
limit_cache_free_space = limit_cache:free_space(),
api_tokens_size = api_tokens:capacity() - api_tokens:free_space(),
}))
}
}八、总结
OpenResty 的 Lua 脚本将 Nginx 从静态配置的代理升级为可编程的智能流量网关。动态限流(Redis 存储阈值)实现了不同用户等级的差异化保护;边缘 JWT 鉴权减少了每次请求都回源验证的开销;AB 分流让新版本可以精准控制实验比例。这些能力在香港服务器的高并发 API 场景中,是区别「够用」和「生产级」的关键差异。