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 文件
agent/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 或者已經遇到這些坑的同學一個提醒。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 04-Consul服務註冊與發現
- Consul 服務的註冊和發現
- 微服務Consul系列之服務註冊與發現微服務
- golang consul-grpc 服務註冊與發現GolangRPC
- consul 服務註冊中心
- consul 作為服務註冊與發現和配置中心
- NodeJs服務註冊與服務發現實現NodeJS
- nacos服務註冊與發現
- Eureka服務註冊與發現
- 聊聊微服務的服務註冊與發現!微服務
- 微服務4:服務註冊與發現微服務
- 基於Docker + Consul + Registrator的服務註冊與發現叢集搭建Docker
- 小白入門微服務(4) - 服務註冊與服務發現微服務
- 小白入門微服務(4) – 服務註冊與服務發現微服務
- Nacos服務註冊與發現的原理
- SpringColud Eureka的服務註冊與發現SpringGC
- SpringCloud服務的註冊與發現(Eureka)SpringGCCloud
- Nacos服務註冊與發現原理
- 服務註冊與發現之ETCD
- 三. SpringCloud服務註冊與發現SpringGCCloud
- etcd與服務註冊及發現
- springcloud之服務註冊與發現SpringGCCloud
- 微服務SpringCloud之服務註冊與發現微服務SpringGCCloud
- 實現etcd服務註冊與發現
- Eureka實現服務註冊與發現
- Consul 學習筆記-服務註冊筆記
- (十四)spring cloud微服務分散式雲架構-服務註冊與發現(Eureka、Consul)SpringCloud微服務分散式架構
- go微服務系列(二) - 服務註冊/服務發現Go微服務
- Choerodon 的微服務之路(三):服務註冊與發現微服務
- NetCore專案實戰篇06---服務註冊與發現之consulNetCore
- go-kit微服務:服務註冊與發現Go微服務
- 服務註冊與發現的原理和實現
- Nacos 服務註冊與發現原理分析
- SpringCloud(3)---Eureka服務註冊與發現SpringGCCloud
- 6.Zookeeper服務註冊與發現
- .Net Core微服務入門全紀錄(二)——Consul-服務註冊與發現(上)微服務
- .Net 6 使用 Consul 實現服務註冊與發現 看這篇就夠了
- .net core學習筆記,元件篇:服務的註冊與發現(Consul)初篇筆記元件