真正的加权轮询需动态权重累积与归一化,而非简单取模;SWRR靠int64整型currentWeight累加减totalWeight实现平滑分布,须用atomic+RWMutex保障并发安全,首次调度前必须初始化currentWeight为0。

直接用 index++ % len(servers) 做加权轮询,线上必出问题:权重漂移、并发错乱、节点宕机后仍被调度。真正的加权轮询不是“按权重重复塞数组”,而是靠动态权重累积与归一化来保障长期比例准确、单次不扎堆。
为什么平滑加权轮询(SWRR)不能靠 time.Sleep 或固定间隔
加权轮询的“平滑”指请求分布的概率密度均匀,不是时间上匀速——硬加 time.Sleep 会卡死整个调度 goroutine,无法响应实时权重变更,也无法并发安全地服务多个请求。
- 每次调度必须是瞬时决策,不能阻塞;延迟控制应交给上层超时或熔断,而非负载均衡器内部
- SWRR 的平滑性来自
currentWeight += weight后再减去totalWeight的数学性质,它天然保证高权重节点不会连续被选中 - 若用
float64存currentWeight,多次累加后浮点误差会导致权重严重偏斜;必须用int64,权重配置可放大 100 倍存整数
如何用 atomic + RWMutex 实现线程安全的 SWRR
Go 没有原生原子 float 操作,但 currentWeight 必须并发安全更新。推荐组合:每个后端独立锁 + 全局读写锁保护列表遍历。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 不要用全局
atomic.Int64管理所有节点的currentWeight——它无法表达“对某个节点单独加权再扣减”的语义 - 每个
Backend结构体带一个sync.Mutex,封装NextWeight(totalWeight int64) int64和SelectAndDec(totalWeight int64) bool - 顶层
Select()方法用sync.RWMutex读保护backends切片,避免扩容时 panic;遍历时对每个 backend 加锁调用NextWeight -
totalWeight必须是常量(所有原始Weight之和),且只在权重全量重载时重新计算,不能每次调度都重算
加权随机比 WRR 更适合动态扩缩容场景
当后端节点频繁上下线、权重实时调整(比如 AI 模型按延迟/成本动态调权),SWRR 的 currentWeight 状态难以快速收敛,此时加权随机更鲁棒。
立即学习“go语言免费学习笔记(深入)”;
- 维护一个原子变量
totalWeight,每次权重变更时用atomic.StoreInt64更新 -
Select()中先rand.Int64n(atomic.LoadInt64(&totalWeight))得到目标权重点,再顺序扫描健康 backend 累加其权重,首次越界即命中 - 必须只在
healthyBackends切片上执行该逻辑,原始列表要经健康检查过滤;健康检查需异步 goroutine 定期执行 HTTP/TCP 探针并更新Alive字段 - 禁用全局
rand.Seed;每个 goroutine 应使用独立rand.New(rand.NewSource(time.Now().UnixNano()))实例
最容易被忽略的是:SWRR 的第一次调度前,所有 currentWeight 必须初始化为 0。否则初始偏置会让低权重节点彻底失权——这个细节在压测中才暴露,但上线后已来不及补救。

















