用平滑加权轮询分散上游请求
nginx 按有效权重累加候选节点的得分,选中最高分节点后减去总权重,让请求按权重交错分配。
工程问题#
不同上游节点的承载能力不同;直接连续派发一整批权重份额会制造短时集中,请求重试、连接上限和故障节点还会改变候选集合。
实际实现#
ngx_http_upstream_get_peer 遍历 peer 链表,先排除本次已尝试、被停用、处于失败窗口或已达连接上限的节点。合格节点的 current_weight 加 effective_weight,选最高分节点,再从其得分减去本轮有效权重之和。
处理流程#
- 筛掉当前不可选或本次已尝试的节点
- 每个候选累加有效权重,记录得分最高者与本轮总权重
- 选中节点扣除总权重,并在本次请求的 tried 位图中标记
源码片段#
固定版本 · 原文摘录565 peer->current_weight += peer->effective_weight;566 total += peer->effective_weight;567568 if (peer->effective_weight < peer->weight) {569 peer->effective_weight++;570 }571572 if (best == NULL || peer->current_weight > best->current_weight) {573 best = peer;574 p = i;575 }对已通过前置可用性检查的候选累加 effective_weight,并记下 current_weight 最大的节点。低于配置值的有效权重在此逐步恢复。
578 if (best == NULL) {579 return NULL;580 }581582 rrp->current = best;583584 n = p / (8 * sizeof(uintptr_t));585 m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));586587 rrp->tried[n] |= m;588589 best->current_weight -= total;590591 if (now - best->checked > best->fail_timeout) {592 best->checked = now;593 }594595 return best;无候选则返回 NULL;成功时标记本次已尝试节点,再从胜者 current_weight 中减去本轮 total,影响下一轮排序。
两个连续源码片段分别展示权重累计和胜者扣分;候选过滤及失败降权请通过来源链接查看完整函数。
收益与取舍#
工程分析带来的收益
- 以累计得分将不同权重的请求份额交错分配
- 选择过程同时纳入失败状态、重试范围和连接上限
付出的代价
- 工程分析:该路径每轮扫描候选链表,选择成本为 O(n)
- 权重表达配置份额,不直接反映请求耗时;完成时间与瞬时负载仍可能不同
- 失败回调会降低 effective_weight;它与配置 weight 并非始终相等
可比较的选择
最少连接按当前连接占用选择节点,更侧重在途负载;仍不能直接估计每个请求的计算量。
一致性哈希侧重相同键的路由稳定性,而非按每次请求重新分配份额。
版本与适用范围#
nginx release-1.26.3 的 HTTP upstream round-robin 路径。比例直觉建立在权重与可用候选相对稳定的条件下;单节点、backup 切换与重试还有外围处理。
参考来源#
源码、官方文档与原始论文src/http/ngx_http_upstream_round_robin.c · ngx_http_upstream_get_peer
src/http/ngx_http_upstream_round_robin.c · ngx_http_upstream_free_round_robin_peer
相关实现与比较#
4 个相关案例伙伴分配器把空闲物理页组织为不同 order 的连续块,并用空闲链表管理同阶块。
limit_req 把漏桶计算、红黑树查状态与队列回收组合起来,按配置的 key 管理请求速率。
QEMU 的 QEMUTimerList 用有序单链表维护定时器,链表头就是下一到期项。
缓存块命中后可能需要从队列中间移除,双向链表让这一步保持常数时间。