夜雨聆风学习资料网

ARTICLE · 1155251

grpc-go Balancer源码走读

grpc-go Balancer源码走读
完整的示例代码可以参考前面的文章go-grpc客户端调用.

可以接着参考上一篇文章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:
Idle:SubConn 已创建,但尚未发起连接。
调用 sd.subConn.Connect(),触发底层 TCP 连接。
scheduleNextConnectionLocked():
设置延时定时器;定时器到期后会再次调用 requestConnectionLocked()。
直接 return,让出锁;等待 SubConn 状态回调或者定时器触发。

两种路径驱动下一个地址:

  1. SubConn 状态回调(变成 Ready / TransientFailure);
  2. schedule 定时器超时。
case TransientFailure:

该地址已经连接失败(还在退避时间内,不能立即重试):

  1. 标记本轮首轮遍历失败;保存错误;
  2. continue:for 循环执行 increment(),游标移动,直接试
下一个地址,不等待定时器。
case Connecting:

该地址正在 TCP 握手建连中,不能立刻试下一个;设置定时器等待,return。 等待:要么 SubConn 上报状态,要么定时器超时,再回来继续处理。

default:

非法状态,打日志直接 return。

endFirstPassIfPossibleLocked方法:

最后调用的时候就可以进行负载均衡了.(不太理解的可以参考前面文章grpc客户端走读)

2.构建失败:

resolverWrapper.start()方法:

build方法(参考dnsBuilder实现):

类似的调用逻辑参考上面的分支.

相关学习资料

暂无相关学习资料