Health().Service() 必须显式传 passingOnly: true,否则可能获取已宕机未清理的服务实例;Consul 默认返回所有状态节点,下游调用仅应连接 passing 实例,且需通过 QueryOptions{PassingOnly: true} 正确启用过滤。

Health().Service() 必须显式传 passingOnly: true
调用 client.Health().Service() 时不加 passingOnly: true,大概率拿到已宕机但还没被 Consul 清理的实例。Consul 默认返回所有状态(passing、warning、critical、unknown)的服务节点,而下游调用只应连 passing 实例。
-
passingOnly是第四个参数,类型为bool,不能省略或靠默认值 - 第三个参数是 tag 过滤字符串(如
"v1,primary"),不是布尔值,填错位置会导致编译通过但逻辑失效 - 示例调用:
nodes, _, err := client.Health().Service("user-service", "", true, &consulapi.QueryOptions{PassingOnly: true})注意:这里用了&consulapi.QueryOptions{PassingOnly: true},这是正确写法;旧版 SDK 中直接传true作为第四个参数也有效,但新版推荐统一走QueryOptions
为什么 PassingOnly: true 不等于自动过滤
PassingOnly: true 是 Consul HTTP API 层的开关,它让 Consul Server 在响应前就筛掉非 passing 节点。如果不设,客户端得自己遍历 nodes 切片,逐个比对 node.Checks[0].Status 是否等于 consulapi.HealthPassing ——这不仅多写逻辑,还容易出错:
- 错把字符串
"passing"和常量consulapi.HealthPassing混用,导致判断失效 - 忽略了
Checks数组可能为空或含多个检查项,取索引[0]panic - 没处理
node.Service.Status字段(该字段在部分版本中才稳定可用)
所以别绕路,直接让 Consul 帮你筛。
常见错误:用 Catalog().Services() 替代 Health().Service()
client.Catalog().Services() 只返回服务名列表(map[string][]string),不含地址、端口、健康状态——它不是服务发现的入口。
- 它适合做“服务存在性校验”或管理后台展示,但不能用于构造下游连接
- 如果你看到代码里先
Catalog().Services()拿到user-service,再手动拼地址去连,说明漏掉了健康状态判断 - 真正要发起 RPC 或 HTTP 请求前,必须走
Health().Service("user-service", "", true, &consulapi.QueryOptions{PassingOnly: true})
gin 启动时注册 + 运行时发现,两处都得盯住 passingOnly
gin 本身不参与服务发现,但你在初始化阶段(比如 InitSrvConn())查服务、构造 grpc.Dial() 时,若没加 PassingOnly: true,第一次就可能连到僵尸实例;更麻烦的是,如果后续做了长连接复用或定时刷新服务列表,每次查询仍需保持该参数。
- 不要只在启动时查一次就缓存死——网络抖动或 Consul agent 重启可能导致短暂状态不一致,建议搭配短 TTL 缓存(如 30s)+ 轮询
- 若用
grpc,可结合google.golang.org/grpc/resolver自定义 resolver,在每次新建连接前重新查Health().Service()并带PassingOnly: true - gin 的 handler 里临时查服务?可以,但要注意并发安全和超时控制,别让一次失败的 Consul 查询拖垮整个 HTTP 请求
Consul 的 passingOnly 不是可选项,是保命开关。漏掉它,等于把负载均衡器交给了一个从不看健康状态的交通协管员。











