這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文:Jaeger源碼分析——服務發現與註冊
聲明
Jaeger官方並沒有明確說明其服務註冊和服務發現的具體使用和介紹,這部分功能是在分析源碼的時候,發現其原理與服務註冊和服務發現類似,所以結合自己對服務註冊和服務發現的認識,做一次總結,有錯還請指點。
TChannel服務註冊和服務發現
Jaeger不藉助第三方工具也能實現服務註冊和服務發現,這部分功能由其依賴的RPC架構提供。
第三方註冊——手動註冊
go run cmd/agent/main.go --collector.host-port=192.168.0.10:14267,192.168.0.11:14267
在啟動agent的時候,可配置多個collector靜態地址,這部分地址會形成一張註冊表。
註冊表
github.com/uber/tchannel-go/peer.go #59type PeerList struct { sync.RWMutex parent *RootPeerList //以hostPort為下標組成註冊表 peersByHostPort map[string]*peerScore //負載平衡實現 peerHeap *peerHeap scoreCalculator ScoreCalculator lastSelected uint64}
github.com/jaegertracing/jaeger/pkg/discovery/peerlistmgr/peer_list_mgr.go #150func (m *PeerListManager) ensureConnections() { peers := m.peers.Copy() minPeers := m.getMinPeers(peers) numConnected, notConnected := m.findConnected(peers) //只要有3個有效服務,就不會進行健全狀態檢查 if numConnected >= minPeers { return } ...... for i := range notConnected { // swap current peer with random from the remaining positions r := i + m.rnd.Intn(len(notConnected)-i) notConnected[i], notConnected[r] = notConnected[r], notConnected[i] // try to connect to current peer (swapped) peer := notConnected[i] m.logger.Info("Trying to connect to peer", zap.String("host:port", peer.HostPort())) //用於控制逾時 ctx, cancel := context.WithTimeout(context.Background(), m.connCheckTimeout) conn, err := peer.GetConnection(ctx) cancel() if err != nil { m.logger.Error("Unable to connect", zap.String("host:port", peer.HostPort()), zap.Duration("connCheckTimeout", m.connCheckTimeout), zap.Error(err)) continue } ...... }}
在註冊表上的地址,TChannel都會進行健全狀態檢查,每秒進行一次,如果0.25秒沒有串連上,視為服務不可用。如果串連成功則保留當前服務執行個體,供agent提交資料使用。
github.com/uber/tchannel-go/connection.go #228func (ch *Channel) newOutboundConnection(timeout time.Duration, hostPort string, events connectionEvents) (*Connection, error) { conn, err := net.DialTimeout("tcp", hostPort, timeout) if err != nil { if ne, ok := err.(net.Error); ok && ne.Timeout() { ch.log.WithFields(LogField{"hostPort", hostPort}, LogField{"timeout", timeout}).Infof("Outbound net.Dial timed out") err = ErrTimeout } return nil, err } return ch.newConnection(conn, hostPort, connectionWaitingToSendInitReq, events), nil}
用戶端服務發現
github.com/uber/tchannel-go/peer.go #149func (l *PeerList) choosePeer(prevSelected map[string]struct{}, avoidHost bool) *Peer { var psPopList []*peerScore var ps *peerScore ...... size := l.peerHeap.Len() for i := 0; i < size; i++ { //把peer從Heap頭部彈出來 popped := l.peerHeap.popPeer() if canChoosePeer(popped.HostPort()) { ps = popped break } psPopList = append(psPopList, popped) } //不符合的放入Heap尾部 for _, p := range psPopList { heap.Push(l.peerHeap, p) } if ps == nil { return nil } //合格打分,再放入Heap尾部 l.peerHeap.pushPeer(ps) ps.chosenCount.Inc() return ps.Peer}
當Agent需要提交資料的時候,會從TChannel的負載平衡擷取peer(服務資訊),當有多個的時候,TChannel通過輪詢方式,查詢peer。實現方式:註冊表把所有peer放入peerHeap,先把peer從頭部彈出,再把peer放回尾部,從而實現輪詢策略的負載平衡。
github.com/uber/tchannel-go/retry.go #212func (ch *Channel) RunWithRetry(runCtx context.Context, f RetriableFunc) error { var err error opts := getRetryOptions(runCtx) rs := ch.getRequestState(opts) defer requestStatePool.Put(rs) //預設重試5次 for i := 0; i < opts.MaxAttempts; i++ { rs.Attempt++ if opts.TimeoutPerAttempt == 0 { err = f(runCtx, rs) } else { attemptCtx, cancel := context.WithTimeout(runCtx, opts.TimeoutPerAttempt) err = f(attemptCtx, rs) cancel() } if err == nil { return nil } if !opts.RetryOn.CanRetry(err) { if ch.log.Enabled(LogLevelInfo) { ch.log.WithFields(ErrField(err)).Info("Failed after non-retriable error.") } return err } ...... } // Too many retries, return the last error return err}
網路之間的通訊避免不了網路異常,所以為了提高可用性,重試是其中一種方式。當從負載平衡擷取peer提交資料到Collector,如果提交失敗,會再從負載平衡擷取peer,最多5次,如果5次都不成功就會放棄這次提交。
Consul+docker 服務註冊和服務發現
使用consul實現服務註冊和服務發現是一件很簡單的事情。很多功能都是開箱即用。
準備工作
docker run -itd --network=backend \-p 8400:8400 -p 8500:8500 -p 8600:53/udp \-h node1 progrium/consul -server -bootstrap -ui-dir /ui
docker run \-itd --network=backend \--name=jaeger-agent \-p5775:5775/udp \-p6831:6831/udp \-p6832:6832/udp \-p5778:5778/tcp \--dns-search="service.consul" --dns=172.18.0.2 \jaegertracing/jaeger-agent \/go/bin/agent-linux --collector.host-port=jaeger-collector:14267
#node1docker run -itd --network=backend \--name=jaeger-collector-node1 \-p :14267 \--dns-search="service.consul" --dns=172.18.0.2 \jaegertracing/jaeger-collector \/go/bin/collector-linux \--span-storage.type=cassandra \--cassandra.keyspace=jaeger_v1_dc \--cassandra.servers=cassandra:9042#node2docker run -itd --network=backend \--name=jaeger-collector-node2 \-p :14267 \--dns-search="service.consul" --dns=172.18.0.2 \jaegertracing/jaeger-collector \/go/bin/collector-linux \--span-storage.type=cassandra \--cassandra.keyspace=jaeger_v1_dc \--cassandra.servers=cassandra:9042
服務註冊——自動註冊
docker run -itd --net=backend --name=registrator \--volume=/var/run/docker.sock:/tmp/docker.sock \gliderlabs/registrator:latest \consul://172.18.0.2:8500
使用consul+docker的形式,只要部署好服務,就會被自動註冊到consul,十分簡單。
註冊表
查看註冊表資訊http://localhost:8500/ui/#/dc1/nodes/node1
可以看到啟動的2個Collector服務ip分別為:172.18.0.5和172.18.0.8
consul提供了很多種健全狀態檢查方式:HTTP、TCP、Docker、Shell和TTL。詳情可以查看官網。
服務端服務發現
Consul相對於Agent和Collector是遠程服務,所以提供了2種服務發現方式:HTTP和DNS,在這裡主要使用是DNS,因為簡單,輕量。
當Agent通過DNS解析出多個IP的時候,Consul會隨機選擇一個IP給Agent實現負載平衡。
由於DNS存在緩衝,所以有可能出現,服務不健康,一樣會被正常解析,所以在預設情況下Consul是沒有設定緩衝時間,TTL為0,但是也考慮到了不緩衝對Consul的壓力,所以開放配置,讓我們去決定緩衝時間點DNS Caching。
總結
TChannel與Consul+docker實現的服務發現和服務註冊中都有他們的優缺點:
服務註冊
TChannel的服務註冊適用於一些基礎服務,例如Jaeger就屬於一種基礎服務,這種服務一旦部署很少會變動。
在現在docker流行的大環境下使用Consul實現的服務註冊會簡單很多,docker有一個特點就是ip地址是動態,所以它很適合業務情境,因為業務經常變動,服務也隨著變化。
健全狀態檢查
TChannel和Consul都提供了健全狀態檢查,但是都只是檢測服務是否正在運行,無法瞭解是否能夠正常處理請求。
服務發現
TChannel使用的是用戶端服務發現,這種方式相對於Consul的服務端服務發現的優點就是沒有了遠程網路開銷,單點問題。同時缺點就是各個語言都需要自己實現註冊表,負載平衡等功能。
Consul使用服務端服務發現,它可以很好的和其他服務結合使用,不需要關係註冊表,負載平衡等。而且關於網路開銷和單點問題都提供了方案。