真平滑加权轮询靠currentWeight动态累积与归一化比较,而非time.Sleep;核心是每轮累加各节点权重、选最大者、再减去totalWeight,需int64精度、并发安全及权重非负校验。

Go 里实现加权轮询,别用 time.Sleep 模拟“平滑”,那只是假平滑,会卡住调度器、不响应权重变更、并发不安全;真平滑靠的是 currentWeight 动态累积与归一化比较。
为什么 time.Sleep 不是平滑加权轮询的解法
很多人看到“平滑”就往调度循环里塞 time.Sleep 或 time.Ticker,结果是:整个 goroutine 阻塞,无法在运行中更新节点权重;多个请求同时进来可能抢到同一个 currentWeight 值,导致选中同一台后端;float64 累加误差随时间放大,最终分配比例严重偏离配置值。
真正的平滑,是让高权重节点被选中的概率密度均匀,不是靠“等时间”来凑间隔。
- 调度必须是无阻塞、瞬时返回的
- 每次
Next()调用都应基于最新权重状态做决策 - 所有节点的
currentWeight必须同步更新,不能只改选中的那个
SmoothWeightedRoundRobin 的核心状态更新逻辑
算法本质是维护每个节点的 currentWeight(运行时累加值)和全局 totalWeight(所有原始权重之和,整数且固定),每轮选出 currentWeight 最大者,再统一更新所有节点状态。
立即学习“go语言免费学习笔记(深入)”;
关键三步(必须按顺序):
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 对每个节点执行
node.currentWeight += node.Weight - 找出
currentWeight最大的节点selected - 对
selected执行selected.currentWeight -= totalWeight,其余节点保持已加权后的值
注意:totalWeight 是静态计算一次的整数和,不能每次重算;currentWeight 初始值必须为 0,否则首轮回合就偏斜。
Go 中如何保证并发安全又不失精度
用 sync.RWMutex 锁整个后端列表太重;用 sync.Mutex 锁单个节点结构体更合理,但要注意更新顺序不能拆开——Next() 必须原子性地完成“读全部 + 选一个 + 写全部”。
推荐结构封装方式:
type Backend struct {
Addr string
Weight int64
mu sync.Mutex
currentW int64 // 用 int64,避免 float64 累加漂移
}
type WRR struct {
backends []*Backend
totalWeight int64
mu sync.RWMutex
}
- 权重配置建议放大 100 倍转
int64,比如 0.25 → 25,保留两位小数精度且无误差 - 权重为 0 的节点不能从列表中删除,否则
totalWeight失准,currentWeight累积错位 - 不要在
Next()里做健康检查或网络探测——那是上层策略的事,WRR 只管调度语义
容易被忽略的边界细节
最常漏掉的是:没校验权重是否全为 0,或存在负数。一旦初始化时传入负权重,currentWeight 累加后可能永远无法超过 0,导致永远选不出节点;而全 0 权重意味着无可用后端,此时应直接返回 nil 并由调用方兜底降级,而不是静默循环或 panic。
另一个隐形坑是:把 currentWeight 更新写成先减后加,或漏掉未选中节点的 += Weight,这会导致后续几轮分配剧烈抖动,长期统计也不再符合权重比。

















