17370845950

Golang如何使用Consul实现微服务的注册与发现
Consul客户端需先启动Agent再初始化,地址配置应显式构造;服务注册必需ID和Name,Address须填可访问IP;发现失败主因是健康检查未通过、未设PassingOnly或DC错误;注销须监听信号手动调用ServiceDeregister。

Consul客户端怎么初始化并连接到Agent

必须先确认本地或远程 Consul Agent 已启动(consul agent -dev 或生产模式),否则 api.NewClient() 会静默失败或超时。默认连接 http://127.0.0.1:8500,若地址不同,需显式传入配置:

cfg := api.DefaultConfig()
cfg.Address = "192.168.1.100:8500"
client, err := api.NewClient(cfg)
if err != nil {
    log.Fatal(err)
}

注意:不建议在 DefaultConfig() 后直接修改字段(如 cfg.Address = ...),应新建配置对象或用 api.Config{Address: ...} 显式构造,避免隐式依赖默认行为。

服务注册时哪些字段是必需的、哪些容易填错

调用 client.Agent().ServiceRegister() 时,IDName 是强制字段;AddressPort 若为空,Consul 会尝试从本地网络接口推断,但多网卡或 Docker 环境下极易注册为 127.0.0.1,导致其他服务无法访问。

  • ID 必须全局唯一,建议包含服务名+主机名+端口,例如 "user-srv-host1-8080"
  • Name 是服务逻辑名,用于发现,例如 "user-service"
  • Address 应填可被其他服务直连的 IP(非 localhost),K8s 中常用 status.hostIP 或 Downward API
  • Check 建议至少配置一个 HTTP 健康检查,否则服务上线即视为健康,掩盖真实故障
reg := &api.AgentServiceRegistration{
    ID:      "order-srv-node1-9001",
    Name:    "order-service",
    Address: "10.0.2.15",
    Port:    9001,
    Check: &api.AgentServiceCheck{
        HTTP:     "http://10.0.2.15:9001/health",
        Timeout:  "2s",
        Interval: "5s",
        DeregisterCriticalServiceAfter: "30s",
    },
}
err := client.Agent().ServiceRegister(reg)

服务发现查不到实例?重点检查这三处

调用 client.Health().Service() 返回空列表,常见原因不是代码写错,而是环境或语义理解偏差:

  • 服务注册后未通过健康检查:检查 Consul UI 的 Services → 状态是否为 passing,或执行 curl http://localhost:8500/v1/health/service/order-service
  • 查询时未设 PassingOnly: true:默认返回所有状态实例,包括 criticalwarning,需显式过滤
  • DNS 或 HTTP 接口使用了错误的 DC(Datacenter):跨 DC 调用需指定 dc 参数,否则只查本 DC
res, _, err := client.Health().Service("user-service", "", false, &api.QueryOptions{
    PassingOnly: true,
    Datacenter:  "dc1",
})
if err != nil {
    log.Fatal(err)
}
for _, srv := range res {
    fmt.Printf("Addr: %s:%d\n", srv.Service.Address, srv.Service.Port)
}

Go 服务如何优雅注销 Consul 注册项

进程退出时不注销,会导致 Consul 中残留“僵尸服务”,影响负载均衡和故障转移。不能只靠 TTL 自动剔除——它有延迟,且无法及时释放资源。

正确做法是在 os.Interruptsyscall.SIGTERM 信号处理中调用 ServiceDeregister

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
go func() {
    <-quit
    client.Agent().ServiceDeregister("order-srv-node1-9001")
    os.Exit(0)
}()

更稳妥的方式是结合 context.WithTimeout 防止注销卡住,尤其在 Agent 不可达时。另外,K8s 环境中还需配合 preStop hook,确保容器终止前注销完成。