Consul服务注册必须显式配置Check字段,否则服务永不被踢出;需设DeregisterCriticalServiceAfter(如"30s")触发自动注销,Timeout必须小于Interval,ID须唯一防冲突,健康接口应返回200或5xx且不依赖外部服务。

Consul服务注册时必须显式配置 check 字段才能触发踢出
Consul不会自动判断服务是否健康——哪怕你用 consul agent 启动了健康检查端点,若注册服务时没声明 check,该服务就永远处于 passing 状态,挂掉也不会被踢出。
常见错误是只写了 health_check 配置但漏掉 check 字段,或误以为 HTTP 探针会自动启用。实际必须在服务注册 JSON 中显式定义:
{
"ID": "myapp-01",
"Name": "myapp",
"Address": "10.0.1.100",
"Port": 8080,
"Check": {
"HTTP": "http://10.0.1.100:8080/health",
"Interval": "5s",
"Timeout": "2s",
"DeregisterCriticalServiceAfter": "30s"
}
}
-
DeregisterCriticalServiceAfter是关键:它决定 Consul 在连续失败后多久真正删除服务实例(而不仅是标记为critical) - 若省略该字段,服务即使持续失败也不会被踢出,只会卡在
critical状态 -
Timeout必须小于Interval,否则 Consul 可能并发发起多次超时请求,干扰判断
Go 客户端注册需用 api.AgentServiceRegistration 而非裸调 API
直接拼 JSON + http.Post 容易遗漏字段、编码错误或忽略 Consul 的重试逻辑;官方 github.com/hashicorp/consul/api 包已封装好语义化结构。
重点注意几个易错参数:
立即学习“go语言免费学习笔记(深入)”;
-
Check.TTL和Check.HTTP互斥:选 HTTP 就别设 TTL,否则注册失败报"only one check type may be specified" -
Check.DeregisterCriticalServiceAfter必须带单位(如"30s"),写成30或"30"会静默忽略 - 如果服务启用了 TLS,
Check.HTTP地址必须是https://,且 Consul agent 需配置verify_https = false或提供 CA 证书
示例片段:
reg := &api.AgentServiceRegistration{
ID: "myapp-" + uuid.New().String(),
Name: "myapp",
Address: "10.0.1.100",
Port: 8080,
Check: &api.AgentServiceCheck{
HTTP: "http://10.0.1.100:8080/health",
Interval: "5s",
Timeout: "2s",
DeregisterCriticalServiceAfter: "30s",
},
}
健康接口返回非 2xx 或超时都会导致状态降级
Consul 的 HTTP 检查逻辑很简单:只要 http.Get 返回 error(连接拒绝、超时)或 status code 不在 200–299 范围,就记一次失败。它不解析响应体内容,也不关心 {"status":"ok"} 这类业务字段。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
所以你的 /health 接口必须满足:
- 成功时返回 HTTP 200,且 body 可为空
- 失败时返回 5xx(不要用 4xx,避免和客户端错误混淆),并尽快返回(别做耗时 DB 查询)
- 避免依赖外部服务(如 Redis、MySQL)做健康判断——它们挂了会导致本服务被连带踢出
一个轻量的 Go 实现示例:
func healthHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
defer cancel()
// 只检查本地状态,不碰下游
if !localCache.Ready() {
http.Error(w, "cache not ready", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
服务重启时 ID 冲突会导致旧实例残留
Consul 用 ID 唯一标识服务实例。如果你每次注册都用固定 ID(比如硬编码 "myapp-001"),而进程异常退出未触发注销,Consul 会保留该 ID 对应的 critical 实例长达 DeregisterCriticalServiceAfter 时间 —— 新进程注册同 ID 会被拒绝,或覆盖失败,造成“新实例不生效、旧实例不消失”的假死现象。
解决方式只有两个:
- 注册时用随机 ID(如
uuid.NewString()),并在进程退出前调用client.Agent().ServiceDeregister(id) - 或使用 Consul 的 TTL 检查模式(配合
Passing心跳),但需额外起 goroutine 定期调用/v1/agent/check/pass/xxx
推荐前者,更可控。记得用 os.Interrupt 和 syscall.SIGTERM 捕获信号,确保注销执行。
最常被忽略的一点:Kubernetes Pod 重启、Docker 容器重建、甚至 kill -9 都无法触发优雅注销 —— 此时只能靠 DeregisterCriticalServiceAfter 自动兜底,所以这个值不能设得过大,否则服务发现延迟明显。

















