标准轮询不适用于权重场景,因其按固定顺序轮询而忽略节点性能差异,导致高配节点空转、低配节点超载;权重轮询通过“累积权重+归一化”机制(currentWeight+=weight后全局减最大值)实现平滑加权调度。

为什么标准轮询不适用于权重场景
标准轮询(Round Robin)每次按固定顺序选一个后端,完全忽略各节点处理能力差异。当后端服务器 CPU、内存或网络带宽不均时,高配节点空转、低配节点超载,502 Bad Gateway 或响应延迟飙升就容易出现。权重轮询的核心是:让高权重节点被选中的概率更高,但又不是简单重复多次——否则会破坏请求的时序平滑性,导致突发流量扎堆。
WeightedRoundRobin 的核心实现逻辑
关键不在“轮”而在“累积权重 + 最小堆/最大步进”。主流做法是使用「当前权重 + 最大权重」双字段法:每个节点维护 currentWeight(当前调度权值,初始为 0)和 weight(配置权重)。每轮选出 currentWeight 最大的节点,选中后执行:currentWeight += weight;所有节点再统一减去本轮最大 currentWeight(即归一化偏移)。这样既保证高权重节点更频繁被选,又避免连续集中调度。
实操建议:
- 用
sync.Mutex保护节点列表读写,尤其在并发调用Select()时 - 权重必须为正整数,0 权重应直接过滤掉,否则
currentWeight永远不更新 - 初始化时把所有
currentWeight设为 0,不要设为weight,否则首轮全选高权节点 - 可加个
Reset()方法,在节点上下线时重置全部currentWeight,防止历史累积值拖慢新节点接入
Go 中一个轻量可嵌入的实现示例
不依赖第三方库,仅用标准库,适合集成进 HTTP 反向代理或 gRPC 负载均衡器:
type WeightedNode struct {
Addr string
Weight int
currentW int // 非导出,内部调度用
}
type WeightedRoundRobin struct {
mu sync.RWMutex
nodes []WeightedNode
}
func (wrr *WeightedRoundRobin) Select() string {
wrr.mu.Lock()
defer wrr.mu.Unlock()
if len(wrr.nodes) == 0 {
return ""
}
var maxNode *WeightedNode
var maxWeight int
for i := range wrr.nodes {
node := &wrr.nodes[i]
node.currentW += node.Weight
if node.currentW > maxWeight {
maxWeight = node.currentW
maxNode = node
}
}
if maxNode == nil {
return ""
}
// 归一化:所有节点减去本轮最大 currentW
for i := range wrr.nodes {
wrr.nodes[i].currentW -= maxWeight
}
return maxNode.Addr
}
注意点:
- 这段代码没做负权校验,上线前务必加
if node.Weight - 如果节点动态增删频繁,建议改用
sync.Map存储并配合版本号控制,避免锁粒度太粗 - 测试时用权重
[3,1,1]运行 10 次,预期分布接近6-2-2,而非严格 3:1:1 —— 因为算法本质是概率趋近,不是硬比例切分
和 grpc-go 或 net/http/httputil 的兼容要点
Go 生态里没有开箱即用的加权轮询负载均衡器,grpc-go 的内置 round_robin 不支持权重,net/http/httputil.NewSingleHostReverseProxy 更只支持单 host。要对接它们,得包装一层:
- 对
grpc-go:实现balancer.Balancer接口,在UpdateClientConnState中解析ServiceConfig里的自定义权重字段,注入到你的WeightedRoundRobin实例 - 对
http.RoundTripper:在RoundTrip方法里调用Select()获取目标地址,再用http.Transport拨号,注意复用连接需基于最终Addr做http.Transport.RegisterProtocol或自定义DialContext - 别在
Select()里做 DNS 解析或健康检查 —— 这些该由上层定期完成并刷新nodes列表,否则每次调度都阻塞
权重不是魔法数字,它需要和真实监控指标(如 CPU 使用率、P99 延迟)联动调整;而算法本身最脆弱的环节,永远是节点状态变更时的 currentWeight 归零时机。


















