完整的示例代码可以参考前面的文章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 处理、地址乱序 ShufflegRFC‑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:IdleSubConn 已创建但尚未发起连接。调用sd.subConn.Connect()触发底层 TCP 连接。scheduleNextConnectionLocked()设置延时定时器定时器到期后会再次调用requestConnectionLocked()。直接 return让出锁等待 SubConn 状态回调或者定时器触发。两种路径驱动下一个地址SubConn 状态回调变成 Ready / TransientFailureschedule 定时器超时。case TransientFailure:该地址已经连接失败还在退避时间内不能立即重试标记本轮首轮遍历失败保存错误continuefor 循环执行 increment()游标移动直接试下一个地址不等待定时器。case Connecting:该地址正在 TCP 握手建连中不能立刻试下一个设置定时器等待return。 等待要么 SubConn 上报状态要么定时器超时再回来继续处理。default:非法状态打日志直接 return。endFirstPassIfPossibleLocked方法:最后调用的时候就可以进行负载均衡了.(不太理解的可以参考前面文章grpc客户端走读)2.构建失败:resolverWrapper.start()方法:build方法(参考dnsBuilder实现):类似的调用逻辑参考上面的分支.