gRPC是当前在net/rpc基础上实现负载均衡最可行、维护成本最低的选择,因其内置round_robin策略、支持动态地址发现与健康反馈,而net/rpc缺乏拦截点、连接池和地址切换钩子,无法满足动态扩缩容需求。

标准库 net/rpc 无法直接做负载均衡,必须换框架或自己封装调度层——gRPC 是当前最可行、维护成本最低的选择。
为什么不能直接在 net/rpc 上加负载逻辑
因为 *rpc.Client 没有拦截点、不支持连接池、也不提供地址切换钩子。你调用 rpc.Dial("tcp", "10.0.1.10:8080") 后,整个生命周期就绑定死这个地址,失败了只能手动重连,没法自动切节点。
- 硬编码地址或靠配置文件轮换,等于放弃动态扩缩容能力
- 试图用 goroutine + channel 包一层“伪负载”,会绕过健康状态同步,容易把请求发到已宕机的节点
- 所有重试、熔断、超时都得重复写,且和底层连接耦合严重
用 gRPC 的 round_robin 策略最简落地
不需要写算法,只要让 resolver 返回多个地址,gRPC 就自动轮询分发。关键不是“怎么实现轮询”,而是“怎么让地址能被正确发现和更新”。
- 禁用默认 DNS 解析:
grpc.WithResolvers(&consulResolver{}),避免dns:///路径干扰 - resolver 必须返回
[]resolver.Address,每个元素带Metadata字段可传权重或标签 - 连接初始化时加
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin": {}}]}`) - 注意:
round_robin是连接粒度,不是每次Call()都换节点;它会在多个底层SubConn间轮,所以需确保客户端复用*grpc.ClientConn
自定义 balancer 实现加权或最少连接
当内置策略不够用时,必须实现 balancer.Builder 和 balancer.Balancer 接口。核心不是选节点,而是如何响应地址变更和健康反馈。
立即学习“go语言免费学习笔记(深入)”;
-
HandleResolvedAddresses触发时,要重建内部节点列表,不能只追加 -
Pick方法返回前,必须检查SubConn.GetState(),跳过TRANSIENT_FAILURE状态的连接 - 加权轮询别用虚拟节点(如复制地址 N 次),推荐平滑加权算法(Smooth Weighted RR),避免权重高但长期空闲
- 最少连接数需要客户端主动上报每个节点的活跃连接数,gRPC 不提供该指标,得靠自定义 metric 或服务端暴露 /health/conn 接口
健康检查不能只靠连接建立成功
gRPC 的 SubConn 状态变化滞后,单纯依赖 READY 会漏掉已卡住但 TCP 连接未断的节点。真实场景中必须叠加主动探测。
- 在 resolver 中启动 goroutine,定期调用各节点的
/healthHTTP 接口或 gRPCHealth.Check方法 - 失败达到阈值后,调用
cc.UpdateState()主动降级对应SubConn到CONNECTING,触发重连 - 不要等
Pick失败才剔除——那已经造成业务请求失败,应该前置过滤
真正难的不是轮询代码几行搞定,而是 resolver 怎么保活、balancer 怎么响应瞬态故障、健康检查怎么跟服务端指标对齐——这些细节没对齐,再“正确”的算法也会在生产里失效。


















