ARTICLE · 1155251
grpc-go Balancer源码走读
可以接着参考上一篇文章grpc-go Resolver源码走读继续学习.
在 clientconn.go 里,resolver 和 balancer 的交接点是updateResolverStateAndUnlock方法。
updateResolverStateAndUnlock方法:
这个方法调用存在两种情况.一种是解析器构建失败调用这个方法.另一种是构建成功调用这个方法.
1.构建成功:

构建成功的话.就会启动一个协程进行监控.
watcher方法:

ReportError方法:
updateResolverStateAndUnlock方法:1.唤醒首次解析阻塞的协程和置空连接:
2.resolver 返回错误场景:
maybeApplyDefaultServiceConfig方法:
applyServiceConfigAndBalancer方法:
3.resolver 成功场景:
3.读取配置.回调balancer:
4.updateClientConnState方法:
5.UpdateClientConnState方法:5.1获取当前正在活跃的balance实例:balToUpdate := gsb.latestBalancer()

5.2是否更换连接:
6.UpdateClientConnState方法(默认实现pickfirstBalancer):6.1加锁、取消连接定时器、空地址分支:


6.2健康检查配置解析:

6.3. Endpoint 处理、地址乱序 Shuffle(gRFC‑A62/A113):
第一步,从 Endpoints 摊平出地址列表(gRFC A61):
if endpoints := state.ResolverState.Endpoints; len(endpoints) != 0 {if cfg.ShuffleAddressList { ...洗牌... }for _, endpoint := range endpoints {newAddrs = append(newAddrs, endpoint.Addresses...) // 按序拼接}} else {newAddrs = state.ResolverState.Addresses // 兼容期:有些 balancer 还没正确传 Endpoints}
洗牌是 service config 里 shuffleAddressList: true 可选开启的,两种实现:
- 加权洗牌(环境变量 PickFirstWeightedShuffling 开启时,A113):每个 endpoint 抽随机数 u,按 u^(1/weight) 算分降序排——权重大的更容易排前。注释里那个 PDF 就是这个经典加权随机采样算法(Efraimids-Spirakis);
- 普通洗牌:等概率打乱。
- 两处都 Clone,因为该共享切片会跟并发读它的调用方形成数据竞争。
第二步,去重(deDupAddresses,:388):同一地址在多个 endpoint 里出现只留一个——SubConn 是按地址存在 AddressMap 里的,不去重会对同一条连接发起多次连接请求。
第三步,IPv4/IPv6 交错(interleaveAddresses,:413,RFC 8305 §4 "Happy Eyeballs"):第一个是 IPv6,就把第一个 IPv4 挪到第二位,两族轮流排。动机:如果前 10 个全次超时才碰到IPv4;交错后第二下就能试到另一族,大幅缩短可感知的连接时间。
6.4核心优化:复用已经 Ready 的连接:

6.5econcileSubConnsLocked 协调 SubConn 集合:

6.6更新状态 Connecting,启动逐个地址尝试连接 startFirstPassLocked:
forceUpdateConcludedStateLocked方法:
UpdateState方法(实现类ccBalancerWrapper):
startFirstPassLocked方法:
requestConnectionLocked方法:

case Idle:sd.subConn.Connect(),触发底层 TCP 连接。scheduleNextConnectionLocked():设置延时定时器;定时器到期后会再次调用 requestConnectionLocked()。两种路径驱动下一个地址:
SubConn 状态回调(变成 Ready / TransientFailure); schedule 定时器超时。
case TransientFailure:该地址已经连接失败(还在退避时间内,不能立即重试):
标记本轮首轮遍历失败;保存错误; continue:for 循环执行 increment(),游标移动,直接试
case Connecting:该地址正在 TCP 握手建连中,不能立刻试下一个;设置定时器等待,return。 等待:要么 SubConn 上报状态,要么定时器超时,再回来继续处理。
default:
非法状态,打日志直接 return。
endFirstPassIfPossibleLocked方法:
最后调用的时候就可以进行负载均衡了.(不太理解的可以参考前面文章grpc客户端走读)

2.构建失败:
resolverWrapper.start()方法:

build方法(参考dnsBuilder实现):
