轮询必须用 atomic.Int32 管理索引,避免并发冲突;随机选择需先过滤健康节点并使用并发安全的 rand.Rand 实例,权重匹配在大规模节点下应避免线性扫描。

轮询和随机算法在 Go 里写几行就能跑通,但直接上线会出问题:并发下选到同一台机器、宕机节点还在被随机命中、权重配置了却没生效——根本原因不是逻辑错,而是状态管理没跟上。
轮询必须用 atomic.Int32 管理索引
很多人照抄教科书写法:index++ % len(servers),一并发就错乱。多个 goroutine 同时读到相同 index,返回同一台后端,流量打偏是常态。
- 必须用
atomic.Int32存当前位置,初始化为0 - 每次取值先
atomic.LoadInt32(¤tIndex),再取模,最后atomic.AddInt32(¤tIndex, 1)——顺序不能反 - 取模用
len(servers),别硬编码;列表动态变化时,得配合sync.RWMutex快照,不能边读边删 - 别在
Next()里做健康检查,那是探活 goroutine 的事;这里只查isHealthy[server]这种只读快照
随机选择器不能直接用 rand.Intn 原始列表
rand.Intn(len(servers)) 看似一行搞定,但只要有一台机器挂了,它就有固定概率选中失败节点,请求失败率直线上升。
- 必须先过滤出健康节点:
healthy := make([]string, 0, len(all)); for _, s := range all { if isHealthy[s] { healthy = append(healthy, s) } } - 全局
rand.Rand不是并发安全的,也不能复用默认实例;高并发下要自己 new:rand.New(rand.NewSource(time.Now().UnixNano())) - 如果节点数常在 50+,别用线性扫描累加权重匹配;小规模(github.com/twmb/algo/alias)
- 权重字段必须是正整数,
0表示临时剔除,不能从列表里删——否则破坏轮询序列连续性
加权轮询别用浮点归一化,用平滑 WRR 防扎堆
按权重重复塞节点(比如权重 5 就 append 5 次)会导致某台机器连续被调用,瞬时压力爆表;用 float64 做比例计算又因精度丢失,长期运行后分配严重倾斜。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 平滑加权轮询(Smooth WRR)更稳:每个节点存
weight和运行时currentWeight,每次调度前所有节点currentWeight += weight,选最大者,然后减去总权重totalWeight -
totalWeight必须是整数且固定不变,不能每次重算——否则漂移 - 别在调度函数里改权重;权重变更应走独立通道,触发快照重建,避免读写冲突
- 如果权重实时变动(如按 CPU 调整),别用别名法——预处理成本高;平滑 WRR 天然适应动态权重
健康检查必须和调度解耦,用快照而非实时探测
把 ping 或 http.Get 塞进 Next() 是最常见错误,一次请求卡住整个负载均衡器,延迟毛刺明显。
- 健康检查必须是独立 goroutine,定期执行(如每 5 秒),结果写入
map[string]bool或带版本号的结构体 - 调度函数只读快照,用
sync.RWMutex或atomic.Value替换,避免读锁阻塞高频调用 - 节点连续失败 3 次,别立刻踢出,先降权到
weight=1观察 2 个周期;误杀比漏判更伤服务稳定性 - 快照更新不能覆盖原 map,要用新建 map + 原子替换,防止迭代中途被修改
真正难的不是“怎么选”,而是“选完之后怎么不卡、不偏、不漏”。并发安全、状态隔离、快照一致性——这些地方写错一行,线上就是 P0 故障。

















