最近使用consul
做爲項目的服務註冊與服務發現的基礎功能。在塔建集羣使用中遇到一些坑,下面一個個的記錄下來。html
consul集羣的node也就是咱們所說的consul實例。集羣由多個node組成,爲了集羣的可用性,須要超過半數的node啓用server。如5個node中建議3個啓用server模式,3個node組成的集羣就2個node啓用server模式。
看到這裏的時候你必定以爲沒有什麼問題呀,可是consul坑就是多。加入你的集羣組成以下:node
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。git
這就是consul集羣多node的坑,由於service底層雖然使用了KV存儲,可是service的KEY與serviceid無關,因此在集羣中能夠重複。github
集羣中只有一個node使用server模式,其餘的都是client模式。缺點很明顯,若是server的node掛了,那麼集羣的可用性就沒有了。golang
相同的客戶端使用相同的node地址,這樣就能夠確保同一個servicename下不存在兩個相同的serviceid。缺點是若是客戶端綁定的node掛了,那麼client就使用。
代碼給出api
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會有較多事件,能夠升級爲若是存在而且是健康的就不容許重複註冊,我使用的就是該方案。app
一開始不少人都會以爲服務出現問題了下架了掛了,那麼就會被移出了。可是在consul中刪除service沒有那麼簡單!
請查看官網文檔:
catalog文檔
agent/service文檔
ui
看着彷佛任選一個就能夠作到正確刪除service了!能夠繼續說一聲,沒有那麼簡單,consul的坑就是多。spa
選擇了/agent/service/deregister/:service_id
接口,會發現你沒法刪除別的node的service。好比10.163.145.117中有個serviceid爲agent_xxxx_v1
,可是客戶端鏈接consul使用的IP爲10.163.145.110,那麼就沒法刪除掉agent_xxxx_v1
。code
沒事不是還有一個接口沒有使用嗎?再來看看/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或者已經遇到這些坑的同窗一個提醒。