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
來源:慕課網


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM