标准 round-robin 在 go 中不够用,因节点性能不均,等权重轮询会压垮弱节点;需自研加权调度器,用累计权重切片+二分查找实现 o(1) 查找,过滤 weight=0 节点防 panic。

为什么标准 round-robin 在 Go 里不够用
因为真实服务节点性能不均等——有的机器 CPU 强、有的带宽小、有的延迟高。硬切 1-2-3-1-2-3,会把过多请求压到弱节点上。net/http.RoundTripper 默认不支持权重,http.DefaultTransport 更是完全没留钩子。你得自己造一个能感知 weight 的调度器,而不是依赖连接池或 DNS 轮询。
如何用 slice + 累计权重实现 O(1) 查找
别用 map 存节点再随机选——权重不是概率分布,而是“该节点本轮应被选中多少次”的比例表达。推荐用累计权重数组 + 二分查找,避免每次遍历:
- 初始化时对节点按
weight排序,构建cumulativeWeights切片,比如[3, 5, 9]表示第 1 个节点占前 3 份、第 2 个占 3~5、第 3 个占 5~9 - 每次请求生成
rand.Intn(totalWeight),用sort.SearchInts()找到插入位置 - 注意:权重为 0 的节点必须过滤掉,否则
totalWeight == 0会导致panic: invalid argument to Intn
weights := []int{3, 1, 2}
cum := make([]int, len(weights))
cum[0] = weights[0]
for i := 1; i < len(weights); i++ {
cum[i] = cum[i-1] + weights[i]
}
total := cum[len(cum)-1]
idx := sort.SearchInts(cum, rand.Intn(total)+1)
goroutine 安全的权重更新怎么处理
线上服务经常要动态增删后端或调整权重,但直接改 slice 会引发并发读写 panic。不能用 sync.RWMutex 粗暴锁整个结构——高并发下成为瓶颈。
- 用原子指针替换:把节点列表封装成结构体,用
atomic.StorePointer写入新实例,读取时atomic.LoadPointer - 权重变更不是实时生效,而是“下次调度开始用新配置”,避免中间态不一致
- 别在调度函数里做 deep copy——复制成本高,应在更新时一次性构造好新切片
HTTP Transport 层怎么接入这个调度器
http.Transport 本身不接受自定义拨号逻辑,但你可以劫持 DialContext。关键不是重写 dial,而是提前决定“这次请求该连哪个地址”:
立即学习“go语言免费学习笔记(深入)”;
- 把加权轮询逻辑包装成函数
NextEndpoint() string,返回形如"10.0.1.5:8080"的地址 - 在
Transport.DialContext里调用它,而不是固定写死某个 host - 注意:如果后端用了 HTTPS,记得同步更新
TLSClientConfig.ServerName,否则证书校验失败报x509: certificate is valid for xxx, not yyy
1、2、3 和设成 100、200、300 效果一样,但后者容易溢出或误导维护者。真正难的是节点健康状态和权重联动,这部分得靠外部探活,调度器本身不该承担故障剔除逻辑。










