Consul HTTP客户端连接池需显式配置,否则默认复用失效;服务注册的Check.HTTP路径须Consul agent可达;查询服务列表必须设PassingOnly:true,避免流量发至不健康实例。

Consul HTTP客户端连接池参数必须显式配置,否则默认复用失效
Go 的 net/http 默认使用全局 http.DefaultClient,而 Consul 官方 SDK(github.com/hashicorp/consul/api)底层直接依赖它。如果不重置 Transport,连接池会退化为每次新建 TCP 连接,高频注册/健康检查时容易触发 too many open files 或延迟飙升。
-
MaxIdleConns建议设为 100+,避免连接频繁创建销毁 -
MaxIdleConnsPerHost必须 ≥MaxIdleConns,否则被 silently 截断 -
IdleConnTimeout推荐 30s~60s,太短导致复用率低,太长可能卡住故障连接 - 务必禁用
ExpectContinueTimeout(设为 0),Consul 健康检查不走 100-continue 流程,留着反而增加延迟
示例配置:
config := consulapi.DefaultConfig()
config.HttpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 200,
MaxIdleConnsPerHost: 200,
IdleConnTimeout: 60 * time.Second,
ExpectContinueTimeout: 0,
},
}
client, _ := consulapi.NewClient(config)
服务注册时的 Check.HTTP 路径必须可被 Consul agent 直接访问
Gin 启动后,Consul 会主动向你填的 Check.HTTP 地址发起 GET 请求(如 http://localhost:8080/health)。这个地址不是给前端调用的,而是 Consul agent 本机网络视角下的可达路径——容器或 k8s 环境下写 localhost 会失败。
- 开发环境可写
http://127.0.0.1:8080/health,但需确保 Consul agent 和 Gin 服务在同一宿主机 - 容器部署时必须用服务实际监听的 IP(如
http://0.0.0.0:8080/health)或通过 host.docker.internal(Docker Desktop) - 路径 handler 必须返回 200,且响应体为空或极简(Consul 不解析 body),超时时间
Check.Timeout建议 ≤Check.Interval的 1/3
Gin 中健康检查路由示例:
r.GET("/health", func(c *gin.Context) {
c.Status(http.StatusOK) // 不要 c.JSON,避免额外序列化开销
})
客户端查询服务列表时 PassingsOnly 参数不能省略
Consul 的 api.Health().Service 默认返回所有状态的服务实例(包括 critical、warning),如果漏掉 PassingOnly: true,你的负载均衡器可能把请求发给已宕机但尚未被剔除的实例。
- 错误写法:
client.Health().Service("user-service", "", false, nil)—— 第三个参数是PassingOnly,false 就是不过滤 - 正确写法:
client.Health().Service("user-service", "", true, nil) - 若需兜底(比如刚启动时无 Passing 实例),应自行加 fallback 逻辑,而非依赖非 Passing 数据
注意:该参数影响的是服务发现结果,和注册时的 Check 配置无关;即使注册时写了完整 Check,查询端不设 PassingOnly: true 仍会拿到脏数据。
Gin 中间件里做服务发现调用需控制超时与重试
在 Gin 的中间件或 handler 里调用 Consul API(如查服务列表、发健康上报)属于外部依赖,不能阻塞主请求流。常见错误是直接用同步阻塞调用,导致整个 HTTP 请求 hang 住。
- 所有 Consul client 方法都支持传入
context.Context,务必带上带 timeout 的 ctx(如ctx, cancel := context.WithTimeout(c.Request.Context(), 500*time.Millisecond)) - 不要在中间件里反复调用
Service()查列表——应结合本地缓存 + 定期后台刷新,或使用Watch()监听变更 - Consul Watch 在连接中断时行为不一致:Consul SDK 会自动重连并补发事件,但你要处理
watcher.ResultCh()关闭后的重建逻辑
关键点:Gin 的 *gin.Context 和 Consul 的 context.Context 是两套东西,前者不能直接传给后者,必须用 c.Request.Context() 桥接。
Consul 集成里最容易被忽略的其实是 Transport 层复用和查询端的 PassingOnly 控制——它们不报错,但会在高并发或故障场景下静默放大问题。


















