NJS可实现基于业务指标的动态加权轮询:通过ngx.shared字典存储节点实时指标(响应时间、错误率、健康状态),在proxy_pass前由choose_backend.js按score = base_weight × (1−err_rate) / (rt_ms/100)公式计算并归一化权重,再执行平滑加权轮询选节点,彻底绕过upstream静态weight限制。

直接用 NJS(Nginx JavaScript)实现基于业务指标的动态加权轮询,核心在于绕过静态 weight 参数,改由 JS 在每次请求时实时计算各节点的调度权重,并结合健康状态、响应延迟、错误率等真实指标动态调整。Nginx 原生加权轮询不支持运行时权重变更,但 NJS 提供了 ngx.shared 字典 + subrequest + 自定义调度逻辑的能力,可构建真正闭环的动态调度系统。
一、NJS 动态加权轮询的核心架构
不依赖 upstream 的 weight 指令,而是把后端列表和实时指标存入共享字典,由 NJS 函数在 proxy_pass 前执行调度决策:
- 用
ngx.shared.upstream_state存储每台后端的:当前权重(current_weight)、最近 30 秒平均响应时间(rt_ms)、5xx 错误率(err_rate)、是否健康(up) - 编写
choose_backend.js:读取所有节点状态,按公式重新归一化权重(例如:score = base_weight * (1 - err_rate) / max(1, rt_ms / 100)),再做平滑加权轮询(Smooth WRR)选节点 - 通过
subrequest或定时timer调用健康检查接口,异步更新共享字典中的指标
二、关键配置与代码示例
需启用 NJS 模块(Nginx ≥ 1.19.2,编译时含 --add-dynamic-module=nginx-plus-module-njs 或使用官方预编译包):
1. nginx.conf 中声明共享字典与加载脚本
js_shared_dict upstream_state 10m;
js_import /etc/nginx/choose_backend.js;
upstream backend_pool {
# 空 upstream,仅作占位;真实转发由 proxy_pass 变量控制
server 127.0.0.1:1; # 防止配置校验失败
}
server {
listen 80;
location / {
js_set $backend choose_backend.choose;
proxy_pass http://$backend;
}
}
2. choose_backend.js 核心逻辑(简化版)
const dict = ngx.shared.upstream_state;
const backends = ['192.168.1.10:8080', '192.168.1.11:8080', '192.168.1.12:8080'];
const baseWeights = { '192.168.1.10:8080': 5, '192.168.1.11:8080': 3, '192.168.1.12:8080': 2 };
<p>function getScore(backend) {
const info = dict.get(backend);
if (!info || !info.up) return 0;
const rt = Math.max(10, info.rt_ms || 100);
const err = info.err_rate || 0;
return baseWeights[backend] * (1 - err) / (rt / 100);
}</p><p>export function choose(r) {
let candidates = [];
for (let b of backends) {
const score = getScore(b);
if (score > 0) candidates.push({ b, score });
}
if (candidates.length === 0) return '192.168.1.10:8080'; // fallback</p><pre class="brush:php;toolbar:false;">// 简单加权随机(适合中小流量)或实现 Smooth WRR 状态机
const total = candidates.reduce((s, c) => s + c.score, 0);
let rand = Math.random() * total;
for (let c of candidates) {
rand -= c.score;
if (rand <p>}</p>三、业务指标采集与更新机制
动态权重的价值取决于指标的真实性和时效性。推荐两种低侵入方式:
-
主动探测:用 NJS
timer每 5 秒对每个后端发 HEAD 请求,记录耗时与状态码,写入upstream_state -
被动埋点:在后端返回头中注入自定义字段(如
X-Backend-Metrics: rt=42,err=0.01),NJS 在log_by_lua_block(若混用 Lua)或通过 NJS 的ngx.req.start_time()+ngx.var.upstream_response_time计算 RT,再聚合统计错误率
注意:共享字典是进程级的,多 worker 间自动同步,无需额外锁机制。
四、上线前必须验证的三点
避免“动态”变成“失控”:
- 设置权重下限(如 score
- 所有后端初始状态必须预设(如首次读取为 null 时,用 baseWeight + 默认 RT=200ms + err=0),否则首请求可能无节点可选
- 添加降级开关:在共享字典中设
dict.set('mode', 'static'),choose()函数检测到后直接走固定轮询,便于故障时快速回滚











