gRPC Go 的 round_robin 不支持权重,需自定义 Balancer;正确实现需预计算累积权重+二分查找、线程安全更新、从 Address.Metadata 解析 weight 字段,并注意 Build/UpdateClientConnState 触发时机与原子性问题。

gRPC 中 round_robin 不支持权重?得自己写 Balancer
Go 默认的 gRPC round_robin 策略只做均匀轮询,完全不认权重。想让服务 A 接 70% 流量、B 接 30%,必须实现自定义 Balancer —— 这不是配置开关,是实打实要注册新类型、接管连接选择逻辑。
实现 Picker 时别直接用 rand.Intn 做加权随机
常见错误是每次 pick 都生成一个随机数,在小流量或连接数少时抖动剧烈,导致实际分布严重偏离预期权重。正确做法是预计算累积权重数组 + 二分查找,保证概率收敛性。
实操建议:
- 维护一个
[]*pickerEntry,每个 entry 包含addr和cumulativeWeight(前缀和) - 每次
Pick时调用rand.Float64() * totalWeight得到目标值,再用sort.SearchFloat64找第一个 ≥ 目标值的索引 - 权重更新必须线程安全:用
sync.RWMutex保护pickerEntries和totalWeight - 避免在
Pick内做任何阻塞操作(如 HTTP 请求、锁竞争),否则拖慢整个 RPC 调用链
Build 和 UpdateClientConnState 的触发时机很关键
这两个方法不是“初始化一次就完事”。Build 在每次调用 grpc.Dial 时执行;而 UpdateClientConnState 在后端地址变更(如 DNS 刷新、etcd watch 更新)或连接状态变化(UP/DOWN)时被 gRPC runtime 主动调用。
立即学习“go语言免费学习笔记(深入)”;
容易踩的坑:
- 在
Build里缓存ClientConn或复用旧Picker实例 → 导致新连接无法感知地址更新 - 没处理
state.ResolverState.Addresses为空的情况 →Pick时 panic - 把权重信息硬编码进
Build参数 → 权重无法动态调整,得重启服务 - 忘记在
UpdateClientConnState中重建Picker→ 新增节点永远接不到流量
权重字段得从 Address.Metadata 解析,不能靠 DNS 名或标签
gRPC Go 不提供原生权重字段,必须借道 resolver.Address.Metadata。服务发现组件(如 consul、nacos)注册实例时,要把权重塞进 metadata,比如:map[string]interface{}{"weight": 70}。
实操注意点:
- metadata 是
interface{},解析前务必做类型断言和默认值兜底,例如:w, ok := md.(map[string]interface{})["weight"]; if !ok { w = 1 } - 不要用字符串 key 如
"WEIGHT",大小写敏感且易拼错,统一用小写"weight" - 如果用
dnsresolver,它不支持传 metadata → 必须换自定义 resolver 或改用passthrough+ 手动构造Address - 权重值建议限制在 1–100 整数范围,避免浮点精度误差放大
真正麻烦的是权重变更的原子性:metadata 更新、address 列表刷新、picker 重建,这三步之间存在微小窗口期,可能短暂出现流量倾斜或 503。生产环境建议配合健康检查 + 平滑摘除做兜底。










