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


    最近使用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或者已经遇到这些坑的同学一个提醒。


    作者:holdtom
    链接:https://www.imooc.com/article/271416
    来源:慕课网

  • 相关阅读:
    2020软件工程个人作业06————软件工程实践总结作业
    2020软件工程作业01
    班级博客V2.1版本更新日志
    博客园班级手机版
    班级帮助文档
    问题累计
    2020 软件工程作业06
    2020 软件工程作业04
    2020 软件工程作业03
    2020软件工程02
  • 原文地址:https://www.cnblogs.com/ExMan/p/11882451.html
Copyright © 2020-2023  润新知