
1. 前言
服務發(fā)現(xiàn)就是服務提供者將自己提供的地址post或者update到服務中介,服務消費者從服務中介那里get自己想要的服務的地址。
但是有兩個問題:
第一個問題:如果有一個服務提供者宕機,那么中介的key/value中會有一個不能訪問的地址,該怎么辦?
心跳機制: 服務提供者需要每隔5秒左右向服務中介匯報存活,服務中介將服務地址和匯報時間記錄在zset數(shù)據(jù)結構的value和score中。服務中介需要每隔10秒左右檢查zset數(shù)據(jù)結構,踢掉匯報時間嚴重落后的地址。這樣就可以保證服務列表中地址的有效性。
第二個問題是服務地址變動時如何通知消費者。有兩種解決方案。
第一種是輪詢,消費者每隔幾秒查詢服務列表是否有改變。如果服務地址很多,查詢會很慢。這時候可以引入服務版本號機制,給每個服務提供一個版本號,在服務變動時,遞增這個版本號。消費者只需要輪詢這個版本號的變動即可知道服務列表是否發(fā)生了變化。
第二種是采用pubsub。這種方式及時性要明顯好于輪詢。缺點是每個pubsub都會占用消費者一個線程和一個額外的連接。為了減少對線程和連接的浪費,我們使用單個pubsub廣播全局版本號的變動。所謂全局版本號就是任意服務列表發(fā)生了變動,這個版本號都會遞增。接收到版本變動的消費者再去檢查各自的依賴服務列表的版本號是否發(fā)生了變動。這種全局版本號也可以用于第一種輪詢方案。
CAP理論
CAP理論是分布式架構中重要理論
- 一致性(Consistency) (所有節(jié)點在同一時間具有相同的數(shù)據(jù))
- 可用性(Availability) (保證每個請求不管成功或者失敗都有響應)
- 分隔容忍(Partition tolerance) (系統(tǒng)中任意信息的丟失或失敗不會影響系統(tǒng)的繼續(xù)運作)
關于P的理解,我覺得是在整個系統(tǒng)中某個部分,掛掉了,或者宕機了,并不影響整個系統(tǒng)的運作或者說使用,而可用性是,某個系統(tǒng)的某個節(jié)點掛了,但是并不影響系統(tǒng)的接受或者發(fā)出請求,CAP 不可能都取,只能取其中2個。原因是
(1)如果C是第一需求的話,那么會影響A的性能,因為要數(shù)據(jù)同步,不然請求結果會有差異,但是數(shù)據(jù)同步會消耗時間,期間可用性就會降低。
(2)如果A是第一需求,那么只要有一個服務在,就能正常接受請求,但是對與返回結果變不能保證,原因是,在分布式部署的時候,數(shù)據(jù)一致的過程不可能想切線路那么快。
(3)再如果,同事滿足一致性和可用性,那么分區(qū)容錯就很難保證了,也就是單點,也是分布式的基本核心,好了,明白這些理論,就可以在相應的場景選取服務注冊與發(fā)現(xiàn)了。
2.Consul、zookeeper、etcd、eureka對比結論
平時經常用到的服務發(fā)現(xiàn)的產品進行下特性的對比,首先看下結論:
| Feature | Consul | Zookeeper | Etcd | Eureka |
|---|---|---|---|---|
| 服務健康檢查 | 服務狀態(tài),內存,硬盤等 | (弱)長連接,keepalive | 連接心跳 | 可配支持 |
| 多數(shù)據(jù)中心 | 支持 | — | — | — |
| kv存儲服務 | 支持 | 支持 | 支持 | — |
| 一致性 | raft | paxos | raft | — |
| CAP定理 | CA | CP | CP | AP |
| 使用接口(多語言能力) | 支持http和dns | 客戶端 | http/grpc | http(sidecar) |
| watch支持 | 全量/支持long polling | 支持 | 支持 long polling | 支持 long polling/大部分增量 |
| 自身監(jiān)控 | metrics | — | metrics | metrics |
| 安全 | acl /https | acl | https支持(弱) | — |
| Spring Cloud集成 | 已支持 | 已支持 | 已支持 | 已支持 |
補充:
(1)運維和開發(fā)如果是 Java 更熟,也更多 Java 的應用,那毫無疑問應該用 ZK;如果是搞 Go 的,那么還是 etcd 吧,畢竟有時候遇到問題還是要看源碼的。
(2)在創(chuàng)建一百萬個或更多鍵時,etcd可以比Zookeeper或Consul穩(wěn)定地提供更好的吞吐量和延遲。此外,它實現(xiàn)了這一目標,只有一半的內存,顯示出更高的效率。但是,還有一些改進的余地,Zookeeper設法通過etcd提供更好的最小延遲,代價是不可預測的平均延遲。
(3)
一致性協(xié)議: etcd 使用 Raft 協(xié)議,Zookeeper 使用 ZAB(類PAXOS協(xié)議),前者容易理解,方便工程實現(xiàn);
運維方面:etcd 方便運維,Zookeeper 難以運維;
數(shù)據(jù)存儲:etcd 多版本并發(fā)控制(MVCC)數(shù)據(jù)模型 , 支持查詢先前版本的鍵值對
項目活躍度:etcd 社區(qū)與開發(fā)活躍,Zookeeper 感覺已經快死了;
API:etcd 提供 HTTP+JSON, gRPC 接口,跨平臺跨語言,Zookeeper 需要使用其客戶端;
訪問安全方面:etcd 支持 HTTPS 訪問,Zookeeper 在這方面缺失;
3. 主流注冊中心產品
3.1 Apache Zookeeper -> CP
與 Eureka 有所不同,Apache Zookeeper 在設計時就緊遵CP原則,即任何時候對 Zookeeper 的訪問請求能得到一致的數(shù)據(jù)結果,同時系統(tǒng)對網絡分割具備容錯性,但是 Zookeeper 不能保證每次服務請求都是可達的。
從 Zookeeper 的實際應用情況來看,在使用 Zookeeper 獲取服務列表時,如果此時的 Zookeeper 集群中的 Leader 宕機了,該集群就要進行 Leader 的選舉,又或者 Zookeeper 集群中半數(shù)以上服務器節(jié)點不可用(例如有三個節(jié)點,如果節(jié)點一檢測到節(jié)點三掛了 ,節(jié)點二也檢測到節(jié)點三掛了,那這個節(jié)點才算是真的掛了),那么將無法處理該請求。所以說,Zookeeper 不能保證服務可用性。
當然,在大多數(shù)分布式環(huán)境中,尤其是涉及到數(shù)據(jù)存儲的場景,數(shù)據(jù)一致性應該是首先被保證的,這也是 Zookeeper 設計緊遵CP原則的另一個原因。
但是對于服務發(fā)現(xiàn)來說,情況就不太一樣了,針對同一個服務,即使注冊中心的不同節(jié)點保存的服務提供者信息不盡相同,也并不會造成災難性的后果。
因為對于服務消費者來說,能消費才是最重要的,消費者雖然拿到可能不正確的服務實例信息后嘗試消費一下,也要勝過因為無法獲取實例信息而不去消費,導致系統(tǒng)異常要好(淘寶的雙十一,京東的618就是緊遵AP的最好參照)。
當master節(jié)點因為網絡故障與其他節(jié)點失去聯(lián)系時,剩余節(jié)點會重新進行l(wèi)eader選舉。問題在于,選舉leader的時間太長,30~120s,而且選舉期間整個zk集群都是不可用的,這就導致在選舉期間注冊服務癱瘓。
在云部署環(huán)境下, 因為網絡問題使得zk集群失去master節(jié)點是大概率事件,雖然服務能最終恢復,但是漫長的選舉事件導致注冊長期不可用是不能容忍的。
3.2 Spring Cloud Eureka -> AP

Spring Cloud Netflix 在設計 Eureka 時就緊遵AP原則。Eureka是在Java語言上,基于Restful Api開發(fā)的服務注冊與發(fā)現(xiàn)組件,由Netflix開源。遺憾的是,目前Eureka僅開源到1.X版本,2.X版本已經宣布閉源。
Eureka Server 也可以運行多個實例來構建集群,解決單點問題,但不同于 ZooKeeper 的選舉 leader 的過程,Eureka Server 采用的是Peer to Peer 對等通信。這是一種去中心化的架構,無 master/slave 之分,每一個 Peer 都是對等的。在這種架構風格中,節(jié)點通過彼此互相注冊來提高可用性,每個節(jié)點需要添加一個或多個有效的 serviceUrl 指向其他節(jié)點。每個節(jié)點都可被視為其他節(jié)點的副本。
在集群環(huán)境中如果某臺 Eureka Server 宕機,Eureka Client 的請求會自動切換到新的 Eureka Server 節(jié)點上,當宕機的服務器重新恢復后,Eureka 會再次將其納入到服務器集群管理之中。當節(jié)點開始接受客戶端請求時,所有的操作都會在節(jié)點間進行復制(replicate To Peer)操作,將請求復制到該 Eureka Server 當前所知的其它所有節(jié)點中。
當一個新的 Eureka Server 節(jié)點啟動后,會首先嘗試從鄰近節(jié)點獲取所有注冊列表信息,并完成初始化。Eureka Server 通過 getEurekaServiceUrls() 方法獲取所有的節(jié)點,并且會通過心跳契約的方式定期更新。
默認情況下,如果 Eureka Server 在一定時間內沒有接收到某個服務實例的心跳(默認周期為30秒),Eureka Server 將會注銷該實例(默認為90秒, eureka.instance.lease-expiration-duration-in-seconds 進行自定義配置)。
當 Eureka Server 節(jié)點在短時間內丟失過多的心跳時,那么這個節(jié)點就會進入自我保護模式。
Eureka的集群中,只要有一臺Eureka還在,就能保證注冊服務可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)。除此之外,Eureka還有一種自我保護機制,如果在15分鐘內超過85%的節(jié)點都沒有正常的心跳,那么Eureka就認為客戶端與注冊中心出現(xiàn)了網絡故障,此時會出現(xiàn)以下幾種情況:
Eureka不再從注冊表中移除因為長時間沒有收到心跳而過期的服務;
Eureka仍然能夠接受新服務注冊和查詢請求,但是不會被同步到其它節(jié)點上(即保證當前節(jié)點依然可用);
當網絡穩(wěn)定時,當前實例新注冊的信息會被同步到其它節(jié)點中;
因此,Eureka可以很好的應對因網絡故障導致部分節(jié)點失去聯(lián)系的情況,而不會像zookeeper那樣使得整個注冊服務癱瘓。
3.3 Consul
Consul 是 HashiCorp 公司推出的開源工具,用于實現(xiàn)分布式系統(tǒng)的服務發(fā)現(xiàn)與配置。Consul 使用 Go 語言編寫,因此具有天然可移植性(支持Linux、windows和Mac OS X)。
Consul采用主從模式的設計,使得集群的數(shù)量可以大規(guī)模擴展,集群間通過RPC的方式調用(HTTP和DNS)。
Consul 內置了服務注冊與發(fā)現(xiàn)框架、分布一致性協(xié)議實現(xiàn)、健康檢查、Key/Value 存儲、多數(shù)據(jù)中心方案,不再需要依賴其他工具(比如 ZooKeeper 等),使用起來也較為簡單。
Consul 遵循CAP原理中的CP原則,保證了強一致性和分區(qū)容錯性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加簡單。雖然保證了強一致性,但是可用性就相應下降了,例如服務注冊的時間會稍長一些,因為 Consul 的 raft 協(xié)議要求必須過半數(shù)的節(jié)點都寫入成功才認為注冊成功 ;在leader掛掉了之后,重新選舉出leader之前會導致Consul 服務不可用。

默認依賴于SDK
Consul本質上屬于應用外的注冊方式,但可以通過SDK簡化注冊流程。而服務發(fā)現(xiàn)恰好相反,默認依賴于SDK,但可以通過Consul Template(下文會提到)去除SDK依賴。

Consul Template
Consul,默認服務調用者需要依賴Consul SDK來發(fā)現(xiàn)服務,這就無法保證對應用的零侵入性。
所幸通過Consul Template,可以定時從Consul集群獲取最新的服務提供者列表并刷新LB配置(比如nginx的upstream),這樣對于服務調用者而言,只需要配置一個統(tǒng)一的服務調用地址即可。
Consul強一致性(C)帶來的是:
服務注冊相比Eureka會稍慢一些。因為Consul的raft協(xié)議要求必須過半數(shù)的節(jié)點都寫入成功才認為注冊成功
Leader掛掉時,重新選舉期間整個consul不可用。保證了強一致性但犧牲了可用性。
Eureka保證高可用(A)和最終一致性:
服務注冊相對要快,因為不需要等注冊信息replicate到其他節(jié)點,也不保證注冊信息是否replicate成功
當數(shù)據(jù)出現(xiàn)不一致時,雖然A, B上的注冊信息不完全相同,但每個Eureka節(jié)點依然能夠正常對外提供服務,這會出現(xiàn)查詢服務信息時如果請求A查不到,但請求B就能查到。如此保證了可用性但犧牲了一致性。
其他方面,eureka就是個servlet程序,跑在servlet容器中; Consul則是go編寫而成。
3.4 etcd
etcd是一個采用http協(xié)議的分布式鍵值對存儲系統(tǒng),因其易用,簡單。很多系統(tǒng)都采用或支持etcd作為服務發(fā)現(xiàn)的一部分,比如kubernetes。但正事因為其只是一個存儲系統(tǒng),如果想要提供完整的服務發(fā)現(xiàn)功能,必須搭配一些第三方的工具。
比如配合etcd、Registrator、confd組合,就能搭建一個非常簡單而強大的服務發(fā)現(xiàn)框架。但這種搭建操作就稍微麻煩了點,尤其是相對consul來說。所以etcd大部分場景都是被用來做kv存儲,比如kubernetes。
etcd 比較多的應用場景是用于服務發(fā)現(xiàn),服務發(fā)現(xiàn) (Service Discovery) 要解決的是分布式系統(tǒng)中最常見的問題之一,即在同一個分布式集群中的進程或服務如何才能找到對方并建立連接。和 Zookeeper 類似,etcd 有很多使用場景,包括:
配置管理
服務注冊發(fā)現(xiàn)
選主
應用調度
分布式隊列
分布式鎖
按照官網給出的數(shù)據(jù), 在 2CPU,1.8G 內存,SSD 磁盤這樣的配置下,單節(jié)點的寫性能可以達到 16K QPS, 而先寫后讀也能達到12K QPS。這個性能還是相當可觀。
etcd 提供了 etcdctl 命令行工具 和 HTTP API 兩種交互方法。etcdctl命令行工具用 go 語言編寫,也是對 HTTP API 的封裝,日常使用起來也更容易。所以這里我們主要使用 etcdctl 命令行工具演示。
4. 參考
(1)注冊中心ZooKeeper、Eureka、Consul 、Nacos對比
https://zhuanlan.zhihu.com/p/165217227?utm_source=wechat_session
(2)常用的服務發(fā)現(xiàn)對比(Consul、zookeeper、etcd、eureka)
https://blog.csdn.net/gaohe7091/article/details/101197107