consul 服务注册与服务发现的巨坑

本贴最后更新于 2594 天前,其中的信息可能已经事过景迁

最近使用 consul 作为项目的服务注册与服务发现的基础功能。在塔建集群使用中遇到一些坑,下面一个个的记录下来。

consul 集群多 node

consul 集群的 node 也就是我们所说的 consul 实例。集群由多个 node 组成,为了集群的可用性,需要超过半数的 node 启用 server。如 5 个 node 中建议 3 个启用 server 模式,3 个 node 组成的集群就 2 个 node 启用 server 模式。
看到这里的时候你一定觉得没有什么问题呀,但是 consul 坑就是多。加入你的集群组成如下:

Node Address Status Type Build Protocol DC Segment BJ-MQTEST-01 10.163.145.117:8301 alive server 1.0.6 2 iget-topology-aliyun <all> BJ-MQTEST-02 10.163.147.47:8301 alive server 1.0.6 2 iget-topology-aliyun <all> BJ-TGO-01 10.163.145.110:8301 alive client 1.0.6 2 iget-topology-aliyun <default>

那么 client 可以使用上述的 3 个 ip 连接到 consul 集群,假设 client A 使用使用 10.163.145.117 注册了 service,重启后使用地址 10.163.145.110 注册之前的 service 信息,此时你就会惊喜的发现,UI 可以同时看到在同一个 servicename 下存在两个相同的 serviceid。

这就是 consul 集群多 node 的坑,因为 service 底层虽然使用了 KV 存储,但是 service 的 KEY 与 serviceid 无关,所以在集群中可以重复。

解决方案一

集群中只有一个 node 使用 server 模式,其他的都是 client 模式。缺点很明显,如果 server 的 node 挂了,那么集群的可用性就没有了。

解决方案二

相同的客户端使用相同的 node 地址,这样就可以确保同一个 servicename 下不存在两个相同的 serviceid。缺点是如果客户端绑定的 node 挂了,那么 client 就使用。
代码给出

package registry import ( "fmt" "math" "net" "sort" "strings" log "github.com/golang/glog" ) type ConsulBind struct { Addr string IpInt float64 } type ConsulBindList []ConsulBind func (s ConsulBindList) Len() int { return len(s) } func (s ConsulBindList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ConsulBindList) Less(i, j int) bool { return s[i].IpInt < s[j].IpInt } func (s ConsulBindList) ToStrings() []string { ret := make([]string, 0, len(s)) for _, cbl := range s { ret = append(ret, cbl.Addr) } return ret } func BingConsulSort(consulAddrs []string) []string { localIpStr, err := GetAgentLocalIP() if err != nil { return consulAddrs } localIp := net.ParseIP(localIpStr) localIpInt := int64(0) if localIp != nil { localIpInt = util.InetAton(localIp) } addrslist := make([]ConsulBind, 0, len(consulAddrs)) for _, addr := range consulAddrs { ads := strings.Split(addr, ":") if len(ads) == 2 { ip := net.ParseIP(ads[0]) if ip != nil { ipInt := util.InetAton(ip) fmt.Println("ip:", ip, ipInt, localIpInt, (ipInt - localIpInt)) addrslist = append(addrslist, ConsulBind{ Addr: addr, IpInt: math.Abs(float64(ipInt - localIpInt)), }) } } } consulBindList := ConsulBindList(addrslist) sort.Sort(consulBindList) log.Infof("sort addrs %v", consulBindList) return consulBindList.ToStrings() }

解决方案三

客户端随机使用集群中的任意一个地址,但是注册之前先判断该 servicename 是否已经存在要注册的 serviceid 了,如果存在就删除重新注册。缺点就是 watch 会有较多事件,可以升级为如果存在并且是健康的就不允许重复注册,我使用的就是该方案。

删除 service

一开始很多人都会觉得服务出现问题了下架了挂了,那么就会被移出了。但是在 consul 中删除 service 没有那么简单!
请查看官网文档:
catalog 文档
Deregister Entity
agent/service 文档
Deregister Service

看着似乎任选一个就可以做到正确删除 service 了!可以继续说一声,没有那么简单,consul 的坑就是多。

选择了 /agent/service/deregister/:service_id 接口,会发现你无法删除别的 node 的 service。比如 10.163.145.117 中有个 serviceid 为 agent_xxxx_v1,但是客户端连接 consul 使用的 IP 为 10.163.145.110,那么就无法删除掉 agent_xxxx_v1

没事不是还有一个接口没有使用吗?再来看看 /catalog/deregister,执行完成后看了 UI,嗯嗯的确是删除了 agent_xxxx_v1。等等。。。 。。。 30s 后发现 agent_xxxx_v1 又出现了,这是怎么回事????

请查看 consul 的 bugUnable to deregister a service #1188

解决方案

第一步:查询出 serviceid 所属的 servicename 所有的列表;
第二步:遍历列表获取到 node 的地址后删除所有的 serviceid;

if len(c.Options.Addrs) > 0 { addrMap := make(map[string]string, len(c.Options.Addrs)) for _, host := range c.Options.Addrs { addr, _, err := net.SplitHostPort(host) if err != nil { log.Warningf("%v is err=%v", host, err) continue } addrMap[addr] = host } rsp, _, _ := c.Client.Health().Service(s.Name, "", false, nil) for _, srsp := range rsp { if srsp.Service.ID == serviceId { if host, ok := addrMap[srsp.Node.Address]; ok { config := consul.DefaultNonPooledConfig() config.Address = host // 创建consul连接 client, err := consul.NewClient(config) if err != nil { log.Warningf("NewClient is err=%v", host, err) } err = client.Agent().ServiceDeregister(serviceId) log.Infof("ServiceDeregister host=%v , serviceId=%v", host, serviceId) } } } } else { err = c.Client.Agent().ServiceDeregister(serviceId) log.Infof("ServiceDeregister serviceId=%v", serviceId) }

可以肯定的是 consul 还有其他的坑的,但是这两个坑让我记忆深刻,记录下来给准备使用 consul 或者已经遇到这些坑的同学一个提醒。

打赏 1 积分后可见
1 积分
  • Consul
    6 引用
  • 服务发现
    1 引用
  • 服务注册
    1 引用
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    499 引用 • 1395 回帖 • 249 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...