Nginx自定义轮询需借助ngx_lua模块,推荐用balancer_by_lua_block在upstream阶段动态选后端,支持IP哈希、加权、健康感知等逻辑;次选用rewrite_by_lua+proxy_pass变量方式,但丧失原生健康检查与连接池特性。

Nginx 默认的轮询(round-robin)是静态、无状态的,无法根据请求内容、后端健康状况或业务规则动态调整。要实现自定义轮询逻辑,需借助 ngx_lua 模块(OpenResty 或编译时启用 Lua 支持),在请求处理阶段介入 upstream 选择过程。核心思路是:**绕过 Nginx 原生 upstream 调度,用 Lua 手动选后端,并通过 balancer_by_lua* 或 rewrite_by_lua* + proxy_pass 变量控制转发目标。**
使用 balancer_by_lua* 实现真正上游级调度
这是最规范、推荐的方式,Nginx 在 upstream 阶段将控制权交给 Lua,由你决定具体选哪个 server。
- 需启用
stream或http块中的upstream,但 不配置任何 server(或仅作占位),实际地址由 Lua 动态构造 - 在 upstream 块内使用
balancer_by_lua_block(OpenResty)或balancer_by_lua_file - Lua 中可读取请求头、参数、IP、时间、自定义共享字典(shared dict)中的权重/状态,实现加权、一致性哈希、故障剔除等逻辑
- 调用
balancer.set_current_peer(addr, port)指定本次请求的后端节点
示例(简单 IP 哈希):
upstream backend {balancer_by_lua_block {
local ip = ngx.var.remote_addr
local servers = {"192.168.1.10:8080", "192.168.1.11:8080", "192.168.1.12:8080"}
local hash = ngx.crc32_short(ip)
local idx = (hash % #servers) + 1
local addr, port = string.match(servers[idx], "(.+):(.+)")
balancer.set_current_peer(addr, port)
}
}
用 rewrite_by_lua* + proxy_pass 变量替代 upstream
适用于简单场景或旧版 Nginx(未支持 balancer_by_lua)。本质是提前计算目标地址,存入变量,再由 proxy_pass 引用。
- 在
location中用rewrite_by_lua*计算目标 host:port,写入自定义变量(如$backend_url) -
proxy_pass http://$backend_url(注意必须带http://协议头) - 缺点:无法复用 Nginx 原生的健康检查、slow-start、keepalive 连接池等 upstream 特性,需自行维护连接和容错
关键配合点与注意事项
-
健康检查不能依赖原生 upstream_check:若用
balancer_by_lua*,需在 Lua 中主动探测或结合 shared dict 缓存节点状态;可用lua-resty-healthcheck库辅助 - 共享状态用 lua_shared_dict:存储各后端的实时权重、错误计数、最近响应时间等,跨 worker 进程可见
- 避免阻塞操作:Lua 中禁止调用耗时函数(如 socket.connect 同步阻塞),应使用 cosocket 异步 API 或预缓存决策结果
- 兜底策略必设:当自定义逻辑异常或所有后端不可用时,应 fallback 到默认地址或返回 503,防止请求挂起
典型自定义逻辑举例
- 按请求路径加权:/api/pay → 优先发往高配节点;/api/static → 发往 CDN 边缘节点
- 灰度路由:读取 cookie 中的 user_id,按 ID 尾号 0–4 走新版本,5–9 走旧版本
- 容量感知调度:从后端定时上报的 metrics 接口拉取 CPU/队列长度,动态降低过载节点权重
- 会话保持增强:除 cookie 外,结合用户地理位置(GeoIP)+ 设备指纹做更稳定的 sticky 分组


















