3、权重轮询算法:加权轮询(WRR)原理、平滑加权轮询(SWRR)实现、Nginx中的SWRR源码分析
3.1 为什么需要权重轮询?
先聊个实际场景。我在做支付网关的时候,后端挂了3台服务器。一台是32核的物理机,两台是8核的虚拟机。你想想看,如果我用普通的轮询,每台机器分到的请求一样多,那32核的机器岂不是在「摸鱼」?
这就是权重轮询的用武之地。说白了,就是给每台机器分配一个权重值,权重高的多干活,权重低的少干活。比如32核的机器权重设成4,8核的机器权重设成1,这样请求分配比例就是4:1:1。
3.2 加权轮询(WRR)的基本原理
加权轮询的实现方式有好几种。我最早接触的是「按权重比例分配」的方式。举个例子:
服务器A:权重 5
服务器B:权重 3
服务器C:权重 2
总权重 = 5 + 3 + 2 = 10
轮询序列:A A A A A B B B C C
这种实现很简单,但有个明显的问题——请求会「扎堆」。你看上面的序列,A连续被选中5次,B连续3次,C连续2次。如果A刚好在高峰期挂了,那连续5个请求都会失败。
我在项目中就踩过这个坑。有一次线上告警,发现某台机器负载突然飙升,查了半天才发现是权重轮询导致的「请求洪峰」。嗯,从那以后我再也不敢用这种朴素的WRR了。
3.3 平滑加权轮询(SWRR)的实现
平滑加权轮询(Smooth Weighted Round Robin)就是为了解决上面那个问题而生的。它的核心思想是:让请求分布得更均匀,避免某台机器被连续命中。
SWRR的算法逻辑是这样的:
- 每个节点维护两个值:当前权重(current_weight) 和 有效权重(effective_weight)
- 每次调度时,所有节点的当前权重都加上自己的有效权重
- 选出当前权重最大的节点
- 被选中的节点,当前权重减去总权重
听起来有点绕?我画个图你就明白了。
你看,经过SWRR处理后,请求序列变成了 A, B, A, C, A, B, A, C, A, B。分布均匀多了吧?
3.4 Nginx中的SWRR源码分析
Nginx的upstream模块里,就用了SWRR算法。我直接带你看看核心代码:
// nginx/src/http/ngx_http_upstream_round_robin.c
ngx_uint_t
ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)
{
ngx_uint_t i, n;
ngx_http_upstream_rr_peer_t *peer;
peer = &rrp->peers->peer[0];
n = rrp->peers->number;
// 关键逻辑:找到当前权重最大的节点
for ( ;; ) {
// 遍历所有节点,累加有效权重
for (i = 0; i < n; i++) {
peer[i].current_weight += peer[i].effective_weight;
// 累加总有效权重
total += peer[i].effective_weight;
// 更新有效权重(如果节点异常,会降低权重)
if (peer[i].effective_weight < peer[i].weight) {
peer[i].effective_weight++;
}
}
// 找到当前权重最大的节点
best = NULL;
for (i = 0; i < n; i++) {
if (best == NULL ||
peer[i].current_weight > best->current_weight) {
best = &peer[i];
}
}
if (best == NULL) {
return NGX_BUSY;
}
// 选中节点,减去总权重
best->current_weight -= total;
// 检查节点是否可用
if (best->fails <= best->max_fails) {
return best->index;
}
// 节点不可用,继续下一轮
}
}
这段代码有几个关键点:
- current_weight:每次调度都会累加,选中后减去总权重
- effective_weight:初始等于weight,但会根据节点健康状态动态调整
- fails/max_fails:Nginx的熔断机制,失败次数超限就暂时不选这个节点
3.5 实战中的坑与优化
我在支付系统里用SWRR时,遇到过几个问题:
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 权重突变 | 某台机器权重从10降到1,导致请求分布剧烈波动 | 使用「渐进式权重调整」,每次只调整20% |
| 节点恢复慢 | 故障节点恢复后,effective_weight需要很长时间才能恢复到正常值 | 增加「快速恢复」机制,连续成功3次就恢复权重 |
| 权重精度 | 权重值很大时(如1000),计算开销增加 | 限制权重范围在1-100之间 |
3.6 总结
SWRR的核心就三句话:
- 累加权重:每次调度,所有节点都加上自己的有效权重
- 选最大值:谁当前权重大,谁就干活
- 减总权重:干完活的节点,减去总权重,给其他人机会
这个算法看起来简单,但实际用起来坑不少。我建议你在实现时,先写单元测试,把各种边界情况都覆盖到。比如所有节点权重相同、某个节点权重为0、节点数量变化等场景。
好了,SWRR就聊到这儿。记住一句话:好的负载均衡算法,不是让所有机器都忙,而是让每台机器都忙得刚刚好。
公众号:蓝海资料掘金营,微信deep3321