Go net/rpc 本身不支持负载均衡,必须改用 gRPC 或第三方库;gRPC 通过客户端侧 round_robin 策略与 resolver(如 Consul)结合实现负载均衡,需注意地址发现、健康检查和连接管理。

Go net/rpc 本身不支持负载均衡,必须自己封装或换库
Go 标准库的 net/rpc 是单连接、无服务发现、无重试、无健康检查的纯点对点协议。它连“客户端连哪个服务器”这个最基础的路由问题都不处理,更别说轮询、加权、一致性哈希这些负载策略了。直接用 rpc.Dial 或 rpc.DialHTTP 永远只能连一个地址,硬编码或写死在配置里就等于放弃了负载能力。
实操建议:
- 别试图给
net/rpc“打补丁”加负载逻辑——它的客户端结构体*rpc.Client是不可扩展的,没有拦截器、中间件或连接池钩子 - 如果必须用标准 RPC 协议(如 JSON-RPC/TCP),改用
gRPC或第三方库(如hashicorp/go-plugin或micro/go-microv1) - 若只是内部微服务间调用,
gRPC是当前最稳妥的选择:原生支持round_robin、pick_first等内置策略,且可通过resolver.Builder接入 Consul/Etcd/ZooKeeper
用 gRPC + round_robin 实现最简可用的 RPC 负载均衡
gRPC 的负载均衡发生在客户端侧,不需要代理层(如 Nginx),只要 DNS 或自定义 resolver 返回多个后端地址,配合 round_robin 策略即可自动分发请求。
常见错误现象:rpc error: code = Unavailable desc = all SubConns are in TransientFailure —— 多数是因为 resolver 没返回有效地址,或所有后端都未通过健康检查。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 启动 gRPC 服务时,确保每个实例注册到服务发现中心(如 etcd),并带 TTL 心跳
- 客户端初始化时禁用 DNS 解析(避免干扰),显式指定 resolver 方案:
conn, err := grpc.Dial("example:///service-name", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(&exampleResolver{})) - 使用内置策略无需额外代码:
conn, err := grpc.Dial("dns:///my-service.example.com", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin": {}}]}`)) - 注意:gRPC 的
round_robin是连接粒度的,不是请求粒度;每个*grpc.ClientConn内部会建立多个底层连接,然后在这些连接上轮询
自己实现轻量级 RPC 客户端负载层(适用于 HTTP/JSON-RPC 场景)
如果你的服务是基于 HTTP 的 JSON-RPC(比如用 gorilla/rpc 或自建 handler),又不想引入 gRPC 生态,可以自己封装一层带简单策略的客户端。
关键点在于:把地址列表、选择逻辑、失败转移(failover)和连接复用(http.Transport)解耦开。
实操建议:
- 用
sync/atomic做原子计数器实现最简轮询:type RoundRobinPicker struct { addrs []string idx uint64 } func (r *RoundRobinPicker) Next() string { n := uint64(len(r.addrs)) if n == 0 { return "" } return r.addrs[atomic.AddUint64(&r.idx, 1)%n] } - 不要在每次请求时新建
http.Client;复用同一个 client,并配好Transport.MaxIdleConnsPerHost防止连接风暴 - 遇到 5xx 或连接超时,应记录失败并临时剔除该节点(例如加个
map[string]time.Time缓存“熔断截止时间”),而不是立刻重试所有节点 - 避免在 picker 中做同步健康检查(如 ping)——会拖慢主请求流;改用后台 goroutine 异步探测
Consul + gRPC resolver 是生产环境最省心的组合
本地测试可以用 DNS 或 mock resolver,但上线后几乎必须对接服务发现系统。Consul 提供 HTTP API 和 DNS 接口,gRPC 官方 consul-resolver(非官方维护)或自行实现 resolver.Builder 都很轻量。
容易被忽略的地方:
- Consul 的服务健康检查默认是 TCP 连通性,对 gRPC 应改用 gRPC Health Checking Protocol(即实现
grpc.health.v1.Healthservice) - gRPC resolver 的
ResolveNow不会主动拉新地址;需要自己定时调用,或监听 Consul 的 watch 事件 - Consul KV 或服务标签(tags)可用于灰度分流,但 gRPC resolver 层不解析 tags —— 必须在 picker 层根据
ServiceEntry.Tags做二次过滤 - 如果用
grpc-gov1.60+,注意WithDefaultServiceConfig已被标记为 deprecated,推荐改用WithDefaultCallOptions+ 自定义 balancer










