要启用服务发现而非直连,需将目标地址设为discovery:///user-service并显式传入grpc.withdiscovery(r)(r为consul.new()等实现),否则即使地址含discovery://也会panic;负载均衡器如wrr仅在服务发现模式下生效。

直接用 grpc.DialContext + grpc.WithDiscovery 就能完成服务间 gRPC 远程调用,不需要手动解析地址或轮询注册中心——Kratos 把服务发现、负载均衡、健康检查都封装进 resolver.Builder 里了。
怎么配置 client 才能走服务发现而不是直连
Kratos 客户端是否启用服务发现,完全取决于你传给 grpc.DialContext 的目标地址格式和配置项:
- 用直连地址:
"127.0.0.1:9001"→ 跳过服务发现,走底层 TCP 直连 - 用发现地址:
"discovery:///user-service"→ 触发 Kratos 的discovery.Builder,开始订阅 Consul/Etcd 中的user-service实例列表 - 必须显式传
grpc.WithDiscovery(r),其中r是实现了registry.Discovery接口的实例(比如consul.New()),否则即使地址带discovery://也会 panic -
grpc.WithBalancerName("wrr")或"round_robin"必须在服务发现模式下才生效;直连模式下 balancer 不起作用
为什么 grpc.DialContext 会自动触发服务发现
这不是 gRPC 原生行为,而是 Kratos 注入的 resolver 机制在起作用:
- Kratos 在初始化时调用
resolver.Register(&discovery.Builder{}),把自定义解析器注册进 gRPC 的 resolver 系统 - 当目标地址 scheme 是
discovery时,gRPC 内部会调用该 Builder 的Build()方法,返回一个resolver.Resolver实例 - 这个 Resolver 内部持有
registry.Watcher,会持续监听注册中心中对应 service key 的变化(新增/下线/元数据更新) - 每次实例列表变更,Resolver 就通过
cc.UpdateState()通知 gRPC 底层重建连接池,同时触发负载均衡器重选节点
常见错误:调用报 rpc error: code = Unavailable desc = connection closed
这通常不是网络问题,而是服务发现链路某处断开了。排查顺序如下:
- 确认
discovery:///xxx中的xxx和服务端注册的registry.ServiceInstance.Name完全一致(大小写、中划线都不能错) - 检查
registry.Discovery实例是否已正确初始化并启动(例如consul.New()后要确保consul.Client可连通) - 看日志里有没有
watcher: no instance found for xxx—— 表示注册中心根本没查到该服务,可能服务没起来,或注册逻辑漏了registry.Register() - 如果服务刚上线但客户端仍连不上,等几秒再试;Kratos 的 Watcher 默认有 1–3 秒延迟,不是实时推送
- 别在测试环境混用
discovery://和直连地址:同一grpc.ClientConn实例不能既走发现又走直连,会冲突
clientOptions 里哪些字段实际影响远程调用行为
clientOptions 是 Kratos 封装的配置载体,真正参与 gRPC 连接构建的是它转成的 grpc.DialOption。关键字段作用如下:
-
endpoint string:决定是直连还是发现;值为"discovery:///user-service"时才激活整套发现逻辑 -
discovery registry.Discovery:必须非 nil,且其Get和Watch方法需能正常返回数据,否则 Dial 失败 -
balancerName string:只在服务发现模式下生效;"wrr"(加权轮询)需配合实例元数据里的weight字段,否则退化为普通轮询 -
filters []selector.NodeFilter:可插拔过滤器,比如按zone或version标签筛选节点,不配置则不过滤
注意:timeout、keepalive 这类参数不在 clientOptions 里,得通过 grpc.WithTimeout、grpc.WithKeepaliveParams 等原生选项传入。











