Consul服务健康检查需显式配置TTL或HTTP类型,否则默认“永远健康”;TTL需定时调用PassTTL续约,HTTP检查需暴露真实就绪状态的/health端点。

健康检查注册时必须显式设置 TTL 或 HTTP 类型
Consul 的服务健康检查不是自动开启的,Golang 服务注册时若没配检查项,Consul 就默认认为服务“永远健康”,哪怕进程已僵死。常见错误是只调用 agent.ServiceRegister 却漏掉 Checks 字段,结果服务列表里显示 passing,但实际请求全 503。
正确做法是在 api.AgentServiceRegistration 结构体中填入 Checks 切片。两种主流方式:
-
TTL检查:适合轻量服务,需服务自身定时调用agent.PassTTL续约,超时即标记为critical -
HTTP检查:Consul 主动轮询/health端点,返回 2xx 才算通过,更可靠但依赖服务暴露 HTTP 接口
示例片段(HTTP 检查):
check := &api.AgentServiceCheck{
HTTP: "http://localhost:8080/health",
Timeout: "5s",
Interval: "10s",
DeregisterCriticalServiceAfter: "90s",
}
reg := &api.AgentServiceRegistration{
ID: "svc-01",
Name: "orders",
Address: "10.0.1.10",
Port: 8080,
Checks: []*api.AgentServiceCheck{check},
}
DeregisterCriticalServiceAfter 不是超时阈值,而是“失联后保留服务记录的时间”
这个字段常被误解为“健康检查失败多少秒就下线”,其实它控制的是:当检查持续失败、状态变为 critical 后,Consul 还会在服务目录里保留该实例多久,之后才彻底删除。它不决定状态变更时机,只决定清理时机。
立即学习“go语言免费学习笔记(深入)”;
典型误用场景:
- 设成
"1s"—— 网络抖动导致短暂失败,服务刚恢复就被删了,DNS 缓存来不及更新,流量继续打过去 - 设成
"24h"—— 实际进程已退出,Consul 还挂着“critical”状态,其他服务发现时仍可能尝试连接
合理值取决于你的部署节奏和故障恢复预期。生产环境建议设为 "90s" 到 "5m",既避免误删,也不让僵尸实例滞留太久。
Golang 客户端调用 PassTTL 必须带完整检查 ID,且不能并发调用
使用 TTL 检查时,服务需定期调用 agent.PassTTL 告知 Consul “我还活着”。但这个接口要求传入的检查 ID 必须与注册时完全一致(包括大小写和前缀),且每次调用会重置倒计时 —— 如果多个 goroutine 同时调用,可能造成时间窗口错乱,Consul 认为续约太频繁而拒绝。
安全做法:
- 检查 ID 建议固定为
"service:" + serviceID格式(如"service:orders-01"),注册和续约保持一致 - 用单个 ticker 控制续约频率,比如每 3 秒调一次,间隔必须小于注册时声明的
TTL(如 TTL=5s,则续约间隔 ≤ 4s) - 调用
PassTTL后检查返回 error,非 200 不代表失败,Consul 返回 200 才表示成功续期
注意:PassTTL 是幂等操作,但并发调用可能触发 Consul 内部限流,导致部分请求返回 429。
HTTP 健康检查路径要区分 readiness 和 liveness
Consul 的 HTTP 检查本质是 readiness probe,它只反映服务能否响应请求,不判断内部状态是否就绪(比如 DB 连接未建立、缓存未加载)。如果把 /health 写成只返回 {"status":"ok"} 的静态响应,那即使数据库连不上,Consul 仍会持续标记为 passing。
真实场景应分层设计:
-
/health/live:仅检查进程存活(如返回 200),用于 Consul 的HTTP检查 -
/health/ready:检查依赖组件(DB、Redis、下游服务),供 Kubernetes 或 Istio 的 readiness probe 使用
Consul 不支持 readiness/liveness 分离,所以必须确保你配置的检查路径真正反映“可服务”状态。否则滚动发布时,新实例还没连上 DB 就被注入流量,直接雪崩。


















