在分布式微服務(wù)架構(gòu)中咐柜,一個應(yīng)用可能由一組職責(zé)單一化的服務(wù)組成澳淑。這時候就需要一個注冊服務(wù)的機制屁擅,注冊某個服務(wù)或者某個節(jié)點是可用的犁罩,還需要一個發(fā)現(xiàn)服務(wù)的機制來找到哪些服務(wù)或者哪些節(jié)點還在提供服務(wù)齐蔽。
在實際應(yīng)用中,通常還都需要一個配置文件告訴我們一些配置信息床估,比如數(shù)據(jù)連接的地址含滴,redis 的地址等等。但很多時候丐巫,我們想要動態(tài)地在不修改代碼的情況下得到這些信息谈况,并且能很好地管理它們勺美。
有了這些需求,于是發(fā)展出了一系列的開源框架碑韵,比如 ZooKeeper, Etcd, Consul 等等赡茸。
這些框架一般會提供這樣的服務(wù):
服務(wù)注冊
主機名,端口號祝闻,版本號占卧,或者一些環(huán)境信息。
服務(wù)發(fā)現(xiàn)
可以讓客戶端拿到服務(wù)端地址联喘。
一個分布式的通用?k/v?存儲系統(tǒng)
基于?Paxos?算法或者?Raft?算法
領(lǐng)導(dǎo)者選舉?(Leader?Election)
其它一些例子:
分布式鎖?(Distributed?locking)
原子廣播?(Atomic?broadcast)
序列號?(Sequence?numbers)
…
我們都知道?CAP?是?Eric?Brewer?提出的分布式系統(tǒng)三要素:
強一致性?(Consistency)
可用性?(Availability)
分區(qū)容忍性?(Partition?Tolerance)
幾乎所有的服務(wù)發(fā)現(xiàn)和注冊方案都是 CP 系統(tǒng)华蜒,也就是說滿足了在同一個數(shù)據(jù)有多個副本的情況下,對于數(shù)據(jù)的更新操作達到最終的效果和操作一份數(shù)據(jù)是一樣的豁遭,但是在出現(xiàn)網(wǎng)絡(luò)分區(qū)(分區(qū)間的節(jié)點無法進行網(wǎng)絡(luò)通信)這樣的故障的時候叭喜,在節(jié)點之間恢復(fù)通信并且同步數(shù)據(jù)之前,一些節(jié)點將會無法提供服務(wù)(一些系統(tǒng)在節(jié)點丟失的情況下支持 stale reads )堤框。
ZooKeeper 作為這類框架中歷史最悠久的之一域滥,是由 Java 編寫。Java 在許多方面非常偉大蜈抓,然而對于這種類型的工作還是顯得有些沉重启绰,也正因為其復(fù)雜性和對新鮮事物的向往,我們第一時間就放棄了選擇它沟使。
本文將從協(xié)議和應(yīng)用層來比較 Etcd 和 Consul委可,并最終給出了筆者的偏好。
Etcd
Etcd 是一個使用 go 語言寫的分布式 k/v 存儲系統(tǒng)腊嗡∽徘悖考慮到它是 coreos 公司的項目,以及在 GitHub 上的 star 數(shù)量 (9000+)燕少,Google 著名的容器管理工具 Kuberbetes 就是基于 Etcd 的卡者。我們最先考慮的就是它。
在介紹 Etcd 之前客们,我們需要了解一些基本的概念崇决。我們知道在單臺服務(wù)器單個進程中維護一個狀態(tài)是很容易的,讀寫的時候不會產(chǎn)生沖突底挫。即便在多進程或者多線程環(huán)境中恒傻,使用鎖機制或者協(xié)程(coroutine)也可以讓讀寫有序地進行。但是在分布式系統(tǒng)中建邓,情況就會復(fù)雜很多盈厘,服務(wù)器可能崩潰,節(jié)點間的機器可能網(wǎng)絡(luò)不通等等官边。所以一致性協(xié)議就是用來在一個集群里的多臺機器中維護一個一致的狀態(tài)沸手。
很多的分布式系統(tǒng)都會采用 Paxos 協(xié)議外遇,但是 Paxos 協(xié)議難以理解,并且在實際實現(xiàn)中差別比較大契吉。所以 Etcd 選擇了 Raft 作為它的一致性協(xié)議臀规。Raft 是 Diego Ongaro 和 John Ousterhout 在 ‘In Search of an Understandable Consensus Algorithm’ 中提出的。它在犧牲很少可用性栅隐,達到相似功能的情況下,對 Paxos 做了很大的優(yōu)化玩徊,并且比 Paxos 簡單易懂很多租悄。
它主要集中在解決兩個問題:
領(lǐng)導(dǎo)者選舉(Leader?Election)
Raft 先通過領(lǐng)導(dǎo)選舉選出一個 Leader,后續(xù)的一致性維護都由 Leader 來完成恩袱,這就簡化了一致性的問題泣棋。Raft 會保證一個時間下只會有一個 Leader,并且在超過一半節(jié)點投票的情況下才會被選為 Leader畔塔。當 Leader 掛掉的時候潭辈,新的 Leader 將會被選出來。
日志復(fù)制?(Log?Replication)
為了維護狀態(tài)澈吨,系統(tǒng)會記錄下來所有的操作命令日志把敢。Leader 在收到客戶端操作命令后,會追加到日志的尾部谅辣。然后 Leader 會向集群里所有其它節(jié)點發(fā)送AppendEntriesRPC請求修赞,每個節(jié)點都通過兩階段提交來復(fù)制命令,這保證了大部分的節(jié)點都能完成桑阶。
在實際的應(yīng)用中柏副,一般 Etcd 集群以 5 個或者 7 個為宜,可以忍受 2 個或者 3 個節(jié)點掛掉蚣录,為什么不是越多越好呢割择?是出于性能的考慮,因為節(jié)點多了以后萎河,日志的復(fù)制會導(dǎo)致 CPU 和網(wǎng)絡(luò)都出現(xiàn)瓶頸荔泳。
Etcd 的 API 比較簡單,可以對一個目錄或者一個 key 進行 GET公壤,PUT换可,DELETE 操作,是基于 HTTP 的厦幅。Etcd 提供 watch 某個目錄或者某個 key 的功能沾鳄,客戶端和 Etcd 集群之間保持著長連接 (long polling)∪泛基于這個長連接译荞,一旦數(shù)據(jù)發(fā)生改變瓤的,客戶端馬上就會收到通知,并且返回的結(jié)果是改變后的值和改變前的值吞歼,這一點在實際應(yīng)用中會很有用(這也是后面的 Consul 的槽點之一)圈膏。
Etcd 的 watch 和在一般情況下不會漏掉任何的變更。因為 Etcd 不僅存儲了當前的鍵值對篙骡,還存儲了最近的變更記錄稽坤,所以如果一個落后于當前狀態(tài)的 watch 還是可以通過遍歷歷史變更記錄來獲取到所有的更新。Etcd 還支持 CompareAndSwap 這個原子操作糯俗,首先對一個 key 進行值比較尿褪,只有結(jié)果一致才會進行下一步的賦值操作。利用這個特性就像利用 x86 的 CAS 實現(xiàn)鎖一樣可以實現(xiàn)分布式鎖得湘。
在 Etcd 中有個 proxy 的概念杖玲,它其實是個轉(zhuǎn)發(fā)服務(wù)器,啟動的時候需要指定集群的地址淘正,然后就可以轉(zhuǎn)發(fā)客戶端的請求到集群摆马,它本身不存儲數(shù)據(jù)。一般來說鸿吆,在每個服務(wù)節(jié)點都會啟動一個 proxy囤采,所以這個 proxy 也是一個本地 proxy,這樣服務(wù)節(jié)點就不需要知道 Etcd 集群的具體地址惩淳,只需要請求本地 proxy斑唬。之前提到過一個 k/v 系統(tǒng)還應(yīng)該支持 leader election,Etcd 可以通過 TTL (time to live) key 來實現(xiàn)黎泣。
Consul
Consul 和 Etcd 一樣也有兩種節(jié)點恕刘,一種叫 client (和 Etcd 的 proxy 一樣) 只負責(zé)轉(zhuǎn)發(fā)請求,另一種是 server抒倚,是真正存儲和處理事務(wù)的節(jié)點褐着。
Consul 使用基于 Serf 實現(xiàn)的 gossip 協(xié)議來管理從屬關(guān)系,失敗檢測托呕,事件廣播等等含蓉。gossip 協(xié)議是一個神奇的一致性協(xié)議,之所以取名叫 gossip 是因為這個協(xié)議是模擬人類中傳播謠言的行為而來项郊。要傳播謠言就要有種子節(jié)點馅扣,種子節(jié)點每秒都會隨機向其它節(jié)點發(fā)送自己所擁有的節(jié)點列表,以及需要傳播的消息着降。任何新加入的節(jié)點差油,就在這種傳播方式下很快地被全網(wǎng)所知道。這個協(xié)議的神奇就在于它從設(shè)計開始就沒想要信息一定要傳遞給所有的節(jié)點,但是隨著時間的增長蓄喇,在最終的某一時刻发侵,全網(wǎng)會得到相同的信息。當然這個時刻可能僅僅存在于理論妆偏,永遠不可達刃鳄。
Consul 使用了兩個不同的 gossip pool,分別叫做 LAN 和 WAN钱骂,這是因為 Consul 原生支持多數(shù)據(jù)中心叔锐。在一個數(shù)據(jù)中心內(nèi)部,LAN gossip pool 包含了這個數(shù)據(jù)中心所有的節(jié)點见秽,包括 proxy 和 servers掌腰。WAN pool 是全局唯一的,所有數(shù)據(jù)中心的 servers 都在這個 pool 中张吉。這兩個 pool 的區(qū)別就是 LAN 處理的是數(shù)據(jù)中心內(nèi)部的失敗檢測,事件廣播等等催植,而 WAN 關(guān)心的是跨數(shù)據(jù)中心的肮蛹。除了 gossip 協(xié)議之外,Consul 還使用了 Raft 協(xié)議來進行 leader election创南,選出 leader 之后復(fù)制日志的過程和 Etcd 基本一致伦忠。
回到應(yīng)用層面上來說,Consul 更像是一個 full stack 的解決方案稿辙,它不僅提供了一致性 k/v 存儲昆码,還封裝了服務(wù)發(fā)現(xiàn),健康檢查邻储,內(nèi)置了 DNS server 等等赋咽。這看上去非常美好,簡直可以開箱即用吨娜。于是脓匿,我們初步選定了 Consul 作為我們的服務(wù)發(fā)現(xiàn)和動態(tài)配置的框架。但現(xiàn)實往往是冰冷的宦赠,在深入研究它的 API 之后發(fā)現(xiàn)了比較多的坑陪毡,可能設(shè)計者有他自己的考慮。
在使用獲取所有 services 的 API 的時候返回的只是所有服務(wù)的名字和 tag勾扭,如果想要每個服務(wù)的具體信息毡琉,你還需要挨個去請求。這在客戶端就會十分不方便妙色,因為在筆者看來桅滋,獲取所有服務(wù)的列表以及具體信息應(yīng)該是一個很常見的需求,并且請求的次數(shù)越少越好身辨。
如果 watch 服務(wù)是否有變化虱歪,當值發(fā)生改變的時候蜂绎,返回的結(jié)果居然是相當于重新讀取所有 services,沒有能夠體現(xiàn)出服務(wù)信息的變化笋鄙,這會導(dǎo)致客戶端很難進行更新操作师枣。
健康檢查是 Consul 的內(nèi)置功能,在注冊一個服務(wù)的時候可以加上自定義的健康檢查萧落,這聽上去很不錯践美。但是如果你忘記給你某個服務(wù)加上健康檢查,那它在各種 API 的返回結(jié)果中變得難以預(yù)料找岖。
…
結(jié)語
在折騰了數(shù)天之后陨倡,最終我們決定回歸 Etcd姊途,事實證明這個決定是正確的座哩。我們基于 Etcd 實現(xiàn)了穩(wěn)定的服務(wù)發(fā)現(xiàn)和動態(tài)配置功能,當然還有一些比較細節(jié)的問題沒有在文中闡述泊柬,歡迎一起探討蜜唾。
本文作者:張陳丞杂曲,點融 social 團隊高級工程師,對很多技術(shù)領(lǐng)域都有興趣袁余,目前專注于分布式系統(tǒng)和機器學(xué)習(xí)擎勘。