go原生net/rpc不支持负载均衡,需结合客户端选节点逻辑与服务端注册发现机制实现;核心是将节点选择前移到客户端,支持轮询、随机、最少连接、加权等策略,并依赖etcd等注册中心实现服务发现与健康探测。

Go 语言原生 net/rpc 不支持负载均衡,但通过组合客户端选节点逻辑 + 服务端注册/发现机制,可以构建轻量、可控的 RPC 负载均衡系统。核心在于把“找哪个服务实例处理请求”这件事,从服务端(如 nginx)前移到客户端或中间协调层,提升灵活性与实时性。
客户端负载均衡:自己选服务节点
客户端维护一份可用服务地址列表(例如从 consul/Etcd/zookeeper 拉取,或静态配置),每次调用前按策略选择一个节点发起 RPC 连接。
- 简单轮询(Round Robin):维护一个原子计数器,每次取模递增,适合各节点性能均等的场景
- 随机(Random):开销最低,适合节点数量稳定、故障率低的集群
- 最少连接(Least Connections):需服务端上报当前并发请求数(如通过健康检查接口),客户端据此选择压力最小的节点
- 加权策略:为不同机器配置权重(如 CPU 核心数、内存大小),按比例分配流量;可结合轮询或随机实现
示例片段(轮询):
var ( addrs = []String{“192.168.1.10:8080”, “192.168.1.11:8080”} mu sync.RWMutex idx uint64 ) func nextAddr() string { mu.Lock() defer mu.Unlock() addr := addrs[idx%uint64(len(addrs))] idx++ return addr }
服务端注册与健康探测:让客户端“知道谁活着”
服务启动时向注册中心(如 etcd)写入自身地址和元数据(版本、权重、标签),并定期续租(TTL)。客户端监听变更或定时拉取,自动剔除失联节点。
立即学习“go语言免费学习笔记(深入)”;
- 使用
clientv3(etcd 官方 Go SDK)注册:写入/services/rpc-service/192.168.1.10:8080,设置 TTL=30s - 健康检查建议采用 TCP 心跳或轻量 http 探针(如
/health),避免阻塞 RPC 主流程 - 注册时可附加自定义字段,如
{"weight": 10, "zone": "shanghai"},供客户端做路由决策
基于 gRPC 的进阶方案(推荐)
若项目允许升级协议,gRPC 原生支持客户端负载均衡插件(balancer.Builder),比原生 net/rpc 更成熟:
- 使用
grpc.WithBalancerName("round_robin")启用内置策略 - 自定义
Resolver实现服务发现(对接 etcd/Consul),动态更新后端地址列表 - 配合
grpc.WithKeepaliveParams和重试策略,提升弱网下稳定性
相比手撸 net/rpc 均衡逻辑,gRPC 生态更完善、TLS/流控/超时控制更精细,中小规模服务推荐优先采用。
关键细节提醒
- 连接复用:每个服务地址应复用
*rpc.Client或 gRPC*grpc.ClientConn,避免频繁建连开销 - 失败转移(Failover):单次调用失败后,不应立即报错,而应尝试列表中下一个节点(需控制重试次数,防止雪崩)
- 一致性哈希(可选):对 key(如用户 ID)做哈希后固定路由,适合带状态或缓存亲和性的场景
- 避免单点注册中心:etcd/Consul 集群部署,客户端配置多个 endpoint,防止单点故障导致全量服务不可见
基本上就这些。不复杂但容易忽略的是健康状态同步延迟和连接池管理——这两项做好,负载均衡才真正可靠。