gRPC客户端需先实现resolver.Builder接口并调用resolver.Register注册,且Scheme()返回值须与Dial URL前缀严格一致;Build方法必须返回支持Watch的Resolver以持续推送地址变更,否则LB无法感知扩缩容。

gRPC客户端怎么注册自定义解析器
Go 的 grpc.Dial 默认只认 dns、passthrough 这类内置方案,想用自己写的服务发现逻辑(比如从 Consul 或 Nacos 拉地址),必须提前注册解析器。不注册就直接传 "myresolver:///",会报错:unknown scheme: myresolver。
实操上分两步:
- 实现
resolver.Builder接口,重点是Build方法返回一个能监听后端变化的resolver.Resolver - 调用
resolver.Register注册,且必须在grpc.Dial之前执行——放错位置是常见坑
示例注册代码:
resolver.Register(&myResolverBuilder{})
注意:builder 的 Scheme() 返回值(比如 "consul")要和 Dial 时的 URL 前缀严格一致,大小写敏感。
立即学习“go语言免费学习笔记(深入)”;
为什么 Resolver.Build 要返回支持 Watch 的 Resolver
gRPC 不是“一次性拉取地址然后缓存”,而是持续监听后端列表变化,并通过 cc.UpdateState 主动通知连接管理器。如果 Build 返回的 resolver 没实现 Watch 方法,或 Watch 直接返回 nil,那负载均衡永远收不到新节点,扩容缩容全失效。
典型错误写法:
func (r *myResolver) Watch(_ context.Context, _ string) (resolver.Watcher, error) {
return nil, nil // ❌ 千万别这么干
}
正确做法是启动 goroutine 定期轮询/监听配置中心,并在地址变更时调用 w.OnUpdate(...)。Watch 返回的 resolver.Watcher 实际上是个通道接口,不是阻塞调用。
如何让 gRPC 使用自定义负载均衡策略
解析器只管“给地址”,真正决定发到哪个后端的是负载均衡器(LB policy)。默认是 pick_first,要换成轮询(round_robin)或自定义策略,得在 Dial 时显式指定:grpc.WithBalancerName("round_robin")。
但注意两点:
- 自定义 LB 策略必须先用
balancer.Register注册,名字和WithBalancerName参数要对上 - 如果用了
round_robin,但解析器只返回单个地址(比如硬编码了"127.0.0.1:8080"),它照样只能转给这一个——LB 策略没地址可轮 -
pick_first是兜底行为,即使你指定了别的策略,只要注册失败或名字拼错,gRPC 就静默降级,不会报错提醒
检查是否生效的小技巧:开启 gRPC 日志(GRPC_GO_LOG_SEVERITY_LEVEL=info),看到类似 lb_policy_delegate.go:... round_robin switched to READY 才算真跑起来了。
自定义解析器 + 自定义 LB 组合时最容易漏的点
两者协作链条是:解析器提供地址列表 → LB 策略创建 SubConn → 解析器后续推送新地址 → LB 策略根据新列表增删 SubConn。中间任意一环断掉,流量就卡住。
高频遗漏项:
- 解析器没调用
cc.UpdateState,LB 根本不知道有新地址 - LB 策略里忘记调用
ac.Connect(),SubConn 处于 IDLE 状态,不发请求 - 解析器返回的地址带非法字符(如空格、下划线),gRPC 内部解析失败,但不报错,只静默跳过该地址
- 多个 resolver 同时注册同名 scheme,后者覆盖前者,调试时容易误判
真实项目里,建议在解析器的 Build 和 LB 的 NewClientConn 里都打日志,确认两端确实被触发,而不是靠猜。










