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

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

最近使用 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 发布的第二款编程语言。

    492 引用 • 1383 回帖 • 374 关注

相关帖子

欢迎来到这里!

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

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