在分布式系統(tǒng)中,一致性算法至關(guān)重要。在所有一致性算法中,Paxos 最負(fù)盛名,它由萊斯利·蘭伯特(Leslie Lamport)于 1990 年提出,是一種基于消息傳遞的一致性算法,被認(rèn)為是類似算法中最有效的。
為陽城等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及陽城網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站建設(shè)、成都做網(wǎng)站、陽城網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
Paxos 算法雖然很有效,但復(fù)雜的原理使它實現(xiàn)起來非常困難,截止目前,實現(xiàn) Paxos 算法的開源軟件很少,比較出名的有 Chubby、LibPaxos。此外,Zookeeper 采用的 ZAB(Zookeeper Atomic Broadcast)協(xié)議也是基于 Paxos 算法實現(xiàn)的,不過 ZAB 對 Paxos 進(jìn)行了很多改進(jìn)與優(yōu)化,兩者的設(shè)計目標(biāo)也存在差異——ZAB 協(xié)議主要用于構(gòu)建一個高可用的分布式數(shù)據(jù)主備系統(tǒng),而 Paxos 算法則是用于構(gòu)建一個分布式的一致性狀態(tài)機(jī)系統(tǒng)。
由于 Paxos 算法過于復(fù)雜、實現(xiàn)困難,極大地制約了其應(yīng)用,而分布式系統(tǒng)領(lǐng)域又亟需一種高效而易于實現(xiàn)的分布式一致性算法,在此背景下,Raft 算法應(yīng)運而生。
Raft 算法在斯坦福 Diego Ongaro 和 John Ousterhout 于 2013 年發(fā)表的《In Search of an Understandable Consensus Algorithm》中提出。相較于 Paxos,Raft 通過邏輯分離使其更容易理解和實現(xiàn),目前,已經(jīng)有十多種語言的 Raft 算法實現(xiàn)框架,較為出名的有 etcd、Consul 。
根據(jù)官方文檔解釋,一個 Raft 集群包含若干節(jié)點,Raft 把這些節(jié)點分為三種狀態(tài):Leader、 Follower、Candidate,每種狀態(tài)負(fù)責(zé)的任務(wù)也是不一樣的。正常情況下,集群中的節(jié)點只存在 Leader 與 Follower 兩種狀態(tài)。
? Leader(領(lǐng)導(dǎo)者) :負(fù)責(zé)日志的同步管理,處理來自客戶端的請求,與Follower保持heartBeat的聯(lián)系;
? Follower(追隨者) :響應(yīng) Leader 的日志同步請求,響應(yīng)Candidate的邀票請求,以及把客戶端請求到Follower的事務(wù)轉(zhuǎn)發(fā)(重定向)給Leader;
? Candidate(候選者) :負(fù)責(zé)選舉投票,集群剛啟動或者Leader宕機(jī)時,狀態(tài)為Follower的節(jié)點將轉(zhuǎn)為Candidate并發(fā)起選舉,選舉勝出(獲得超過半數(shù)節(jié)點的投票)后,從Candidate轉(zhuǎn)為Leader狀態(tài)。
通常,Raft 集群中只有一個 Leader,其它節(jié)點都是 Follower。Follower 都是被動的,不會發(fā)送任何請求,只是簡單地響應(yīng)來自 Leader 或者 Candidate 的請求。Leader 負(fù)責(zé)處理所有的客戶端請求(如果一個客戶端和 Follower 聯(lián)系,那么 Follower 會把請求重定向給 Leader)。
為簡化邏輯和實現(xiàn),Raft 將一致性問題分解成了三個相對獨立的子問題。
? 選舉(Leader Election) :當(dāng) Leader 宕機(jī)或者集群初創(chuàng)時,一個新的 Leader 需要被選舉出來;
? 日志復(fù)制(Log Replication) :Leader 接收來自客戶端的請求并將其以日志條目的形式復(fù)制到集群中的其它節(jié)點,并且強(qiáng)制要求其它節(jié)點的日志和自己保持一致;
? 安全性(Safety) :如果有任何的服務(wù)器節(jié)點已經(jīng)應(yīng)用了一個確定的日志條目到它的狀態(tài)機(jī)中,那么其它服務(wù)器節(jié)點不能在同一個日志索引位置應(yīng)用一個不同的指令。
根據(jù) Raft 協(xié)議,一個應(yīng)用 Raft 協(xié)議的集群在剛啟動時,所有節(jié)點的狀態(tài)都是 Follower。由于沒有 Leader,F(xiàn)ollowers 無法與 Leader 保持心跳(Heart Beat),因此,F(xiàn)ollowers 會認(rèn)為 Leader 已經(jīng)下線,進(jìn)而轉(zhuǎn)為 Candidate 狀態(tài)。然后,Candidate 將向集群中其它節(jié)點請求投票,同意自己升級為 Leader。如果 Candidate 收到超過半數(shù)節(jié)點的投票(N/2 + 1),它將獲勝成為 Leader。
第一階段:所有節(jié)點都是 Follower。
上面提到,一個應(yīng)用 Raft 協(xié)議的集群在剛啟動(或 Leader 宕機(jī))時,所有節(jié)點的狀態(tài)都是 Follower,初始 Term(任期)為 0。同時啟動選舉定時器,每個節(jié)點的選舉定時器超時時間都在 100~500 毫秒之間且并不一致(避免同時發(fā)起選舉)。
第二階段:Follower 轉(zhuǎn)為 Candidate 并發(fā)起投票。
沒有 Leader,F(xiàn)ollowers 無法與 Leader 保持心跳(Heart Beat),節(jié)點啟動后在一個選舉定時器周期內(nèi)未收到心跳和投票請求,則狀態(tài)轉(zhuǎn)為候選者 Candidate 狀態(tài),且 Term 自增,并向集群中所有節(jié)點發(fā)送投票請求并且重置選舉定時器。
注意,由于每個節(jié)點的選舉定時器超時時間都在 100-500 毫秒之間,且彼此不一樣,以避免所有 Follower 同時轉(zhuǎn)為 Candidate 并同時發(fā)起投票請求。換言之,最先轉(zhuǎn)為 Candidate 并發(fā)起投票請求的節(jié)點將具有成為 Leader 的“先發(fā)優(yōu)勢”。
第三階段:投票策略。
節(jié)點收到投票請求后會根據(jù)以下情況決定是否接受投票請求(每個 follower 剛成為 Candidate 的時候會將票投給自己):
請求節(jié)點的 Term 大于自己的 Term,且自己尚未投票給其它節(jié)點,則接受請求,把票投給它;
請求節(jié)點的 Term 小于自己的 Term,且自己尚未投票,則拒絕請求,將票投給自己。
第四階段:Candidate 轉(zhuǎn)為 Leader。
一輪選舉過后,正常情況下,會有一個 Candidate 收到超過半數(shù)節(jié)點(N/2 + 1)的投票,它將勝出并升級為 Leader。然后定時發(fā)送心跳給其它的節(jié)點,其它節(jié)點會轉(zhuǎn)為 Follower 并與 Leader 保持同步,到此,本輪選舉結(jié)束。
注意:有可能一輪選舉中,沒有 Candidate 收到超過半數(shù)節(jié)點投票,那么將進(jìn)行下一輪選舉。
在一個 Raft 集群中,只有 Leader 節(jié)點能夠處理客戶端的請求(如果客戶端的請求發(fā)到了 Follower,F(xiàn)ollower 將會把請求重定向到 Leader) ,客戶端的每一個請求都包含一條被復(fù)制狀態(tài)機(jī)執(zhí)行的指令。Leader 把這條指令作為一條新的日志條目(Entry)附加到日志中去,然后并行得將附加條目發(fā)送給 Followers,讓它們復(fù)制這條日志條目。
當(dāng)這條日志條目被 Followers 安全復(fù)制,Leader 會將這條日志條目應(yīng)用到它的狀態(tài)機(jī)中,然后把執(zhí)行的結(jié)果返回給客戶端。如果 Follower 崩潰或者運行緩慢,再或者網(wǎng)絡(luò)丟包,Leader 會不斷得重復(fù)嘗試附加日志條目(盡管已經(jīng)回復(fù)了客戶端)直到所有的 Follower 都最終存儲了所有的日志條目,確保強(qiáng)一致性。
第一階段:客戶端請求提交到 Leader。
如下圖所示,Leader 收到客戶端的請求,比如存儲數(shù)據(jù) 5。Leader 在收到請求后,會將它作為日志條目(Entry)寫入本地日志中。需要注意的是,此時該 Entry 的狀態(tài)是未提交(Uncommitted),Leader 并不會更新本地數(shù)據(jù),因此它是不可讀的。
第二階段:Leader 將 Entry 發(fā)送到其它 Follower
Leader 與 Followers 之間保持著心跳聯(lián)系,隨心跳 Leader 將追加的 Entry(AppendEntries)并行地發(fā)送給其它的 Follower,并讓它們復(fù)制這條日志條目,這一過程稱為復(fù)制(Replicate)。
有幾點需要注意:
1. 為什么 Leader 向 Follower 發(fā)送的 Entry 是 AppendEntries 呢?
因為 Leader 與 Follower 的心跳是周期性的,而一個周期間 Leader 可能接收到多條客戶端的請求,因此,隨心跳向 Followers 發(fā)送的大概率是多個 Entry,即 AppendEntries。當(dāng)然,在本例中,我們假設(shè)只有一條請求,自然也就是一個Entry了。
2. Leader 向 Followers 發(fā)送的不僅僅是追加的 Entry(AppendEntries)。
在發(fā)送追加日志條目的時候,Leader 會把新的日志條目緊接著之前條目的索引位置(prevLogIndex), Leader 任期號(Term)也包含在其中。如果 Follower 在它的日志中找不到包含相同索引位置和任期號的條目,那么它就會拒絕接收新的日志條目,因為出現(xiàn)這種情況說明 Follower 和 Leader 不一致。
3. 如何解決 Leader 與 Follower 不一致的問題?
在正常情況下,Leader 和 Follower 的日志保持一致,所以追加日志的一致性檢查從來不會失敗。然而,Leader 和 Follower 一系列崩潰的情況會使它們的日志處于不一致狀態(tài)。Follower可能會丟失一些在新的 Leader 中有的日志條目,它也可能擁有一些 Leader 沒有的日志條目,或者兩者都發(fā)生。丟失或者多出日志條目可能會持續(xù)多個任期。
要使 Follower 的日志與 Leader 恢復(fù)一致,Leader 必須找到最后兩者達(dá)成一致的地方(說白了就是回溯,找到兩者最近的一致點),然后刪除從那個點之后的所有日志條目,發(fā)送自己的日志給 Follower。所有的這些操作都在進(jìn)行附加日志的一致性檢查時完成。
Leader 為每一個 Follower 維護(hù)一個 nextIndex,它表示下一個需要發(fā)送給 Follower 的日志條目的索引地址。當(dāng)一個 Leader 剛獲得權(quán)力的時候,它初始化所有的 nextIndex 值,為自己的最后一條日志的 index 加 1。如果一個 Follower 的日志和 Leader 不一致,那么在下一次附加日志時一致性檢查就會失敗。在被 Follower 拒絕之后,Leader 就會減小該 Follower 對應(yīng)的 nextIndex 值并進(jìn)行重試。最終 nextIndex 會在某個位置使得 Leader 和 Follower 的日志達(dá)成一致。當(dāng)這種情況發(fā)生,附加日志就會成功,這時就會把 Follower 沖突的日志條目全部刪除并且加上 Leader 的日志。一旦附加日志成功,那么 Follower 的日志就會和 Leader 保持一致,并且在接下來的任期繼續(xù)保持一致。
第三階段:Leader 等待 Followers 回應(yīng)。
Followers 接收到 Leader 發(fā)來的復(fù)制請求后,有兩種可能的回應(yīng):
寫入本地日志中,返回 Success;
一致性檢查失敗,拒絕寫入,返回 False,原因和解決辦法上面已做了詳細(xì)說明。
需要注意的是,此時該 Entry 的狀態(tài)也是未提交(Uncommitted)。完成上述步驟后,F(xiàn)ollowers 會向 Leader 發(fā)出 Success 的回應(yīng),當(dāng) Leader 收到大多數(shù) Followers 的回應(yīng)后,會將第一階段寫入的 Entry 標(biāo)記為提交狀態(tài)(Committed),并把這條日志條目應(yīng)用到它的狀態(tài)機(jī)中。
第四階段:Leader 回應(yīng)客戶端。
完成前三個階段后,Leader會向客戶端回應(yīng) OK,表示寫操作成功。
第五階段,Leader 通知 Followers Entry 已提交
Leader 回應(yīng)客戶端后,將隨著下一個心跳通知 Followers,F(xiàn)ollowers 收到通知后也會將 Entry 標(biāo)記為提交狀態(tài)。至此,Raft 集群超過半數(shù)節(jié)點已經(jīng)達(dá)到一致狀態(tài),可以確保強(qiáng)一致性。
需要注意的是,由于網(wǎng)絡(luò)、性能、故障等各種原因?qū)е隆胺磻?yīng)慢”、“不一致”等問題的節(jié)點,最終也會與 Leader 達(dá)成一致。
前面描述了 Raft 算法是如何選舉 Leader 和復(fù)制日志的。然而,到目前為止描述的機(jī)制并不能充分地保證每一個狀態(tài)機(jī)會按照相同的順序執(zhí)行相同的指令。例如,一個 Follower 可能處于不可用狀態(tài),同時 Leader 已經(jīng)提交了若干的日志條目;然后這個 Follower 恢復(fù)(尚未與 Leader 達(dá)成一致)而 Leader 故障;如果該 Follower 被選舉為 Leader 并且覆蓋這些日志條目,就會出現(xiàn)問題,即不同的狀態(tài)機(jī)執(zhí)行不同的指令序列。
鑒于此,在 Leader 選舉的時候需增加一些限制來完善 Raft 算法。這些限制可保證任何的 Leader 對于給定的任期號(Term),都擁有之前任期的所有被提交的日志條目(所謂 Leader 的完整特性)。關(guān)于這一選舉時的限制,下文將詳細(xì)說明。
在所有基于 Leader 機(jī)制的一致性算法中,Leader 都必須存儲所有已經(jīng)提交的日志條目。為了保障這一點,Raft 使用了一種簡單而有效的方法,以保證所有之前的任期號中已經(jīng)提交的日志條目在選舉的時候都會出現(xiàn)在新的 Leader 中。換言之,日志條目的傳送是單向的,只從 Leader 傳給 Follower,并且 Leader 從不會覆蓋自身本地日志中已經(jīng)存在的條目。
Raft 使用投票的方式來阻止一個 Candidate 贏得選舉,除非這個 Candidate 包含了所有已經(jīng)提交的日志條目。Candidate 為了贏得選舉必須聯(lián)系集群中的大部分節(jié)點。這意味著每一個已經(jīng)提交的日志條目肯定存在于至少一個服務(wù)器節(jié)點上。如果 Candidate 的日志至少和大多數(shù)的服務(wù)器節(jié)點一樣新(這個新的定義會在下面討論),那么它一定持有了所有已經(jīng)提交的日志條目(多數(shù)派的思想)。投票請求的限制中請求中包含了 Candidate 的日志信息,然后投票人會拒絕那些日志沒有自己新的投票請求。
Raft 通過比較兩份日志中最后一條日志條目的索引值和任期號,確定誰的日志比較新。如果兩份日志最后條目的任期號不同,那么任期號大的日志更加新。如果兩份日志最后的條目任期號相同,那么日志比較長的那個就更加新。
如同 4.1 節(jié)介紹的那樣,Leader 知道一條當(dāng)前任期內(nèi)的日志記錄是可以被提交的,只要它被復(fù)制到了大多數(shù)的 Follower 上(多數(shù)派的思想)。如果一個 Leader 在提交日志條目之前崩潰了,繼任的 Leader 會繼續(xù)嘗試復(fù)制這條日志記錄。然而,一個 Leader 并不能斷定被保存到大多數(shù) Follower 上的一個之前任期里的日志條目 就一定已經(jīng)提交了。這很明顯,從日志復(fù)制的過程可以看出。
鑒于上述情況,Raft 算法不會通過計算副本數(shù)目的方式去提交一個之前任期內(nèi)的日志條目。只有 Leader 當(dāng)前任期里的日志條目通過計算副本數(shù)目可以被提交;一旦當(dāng)前任期的日志條目以這種方式被提交,那么由于日志匹配特性,之前的日志條目也都會被間接的提交。在某些情況下,Leader 可以安全地知道一個老的日志條目是否已經(jīng)被提交(只需判斷該條目是否存儲到所有節(jié)點上),但是 Raft 為了簡化問題使用了一種更加保守的方法。
當(dāng) Leader 復(fù)制之前任期里的日志時,Raft 會為所有日志保留原始的任期號,這在提交規(guī)則上產(chǎn)生了額外的復(fù)雜性。但是,這種策略更加容易辨別出日志,即使隨著時間和日志的變化,日志仍維護(hù)著同一個任期編號。此外,該策略使得新 Leader 只需要發(fā)送較少日志條目。
raft 的讀寫都在 leader 節(jié)點中進(jìn)行,它保證了讀的都是最新的值,它是符合強(qiáng)一致性的(線性一致性),raft 除了這個還在【客戶端交互】那塊也做了一些保證,詳情可以參考論文。但是 zookeeper 不同,zookeeper 寫在 leader,讀可以在 follower 進(jìn)行,可能會讀到了舊值,它不符合強(qiáng)一致性(只考慮寫一致性,不考慮讀一致性),但是 zookeeper 去 follower 讀可以有效提升讀取的效率。
對比于 zab、raft,我們發(fā)現(xiàn)他們選舉、setData 都是需要過半機(jī)制才行,所以他們針對網(wǎng)絡(luò)分區(qū)的處理方法都是一樣的。
一個集群的節(jié)點經(jīng)過網(wǎng)絡(luò)分區(qū)后,如一共有 A、B、C、D、E 5個節(jié)點,如果 A 是 leader,網(wǎng)絡(luò)分區(qū)為 A、B、C 和 D、E,在A、B、C分區(qū)還是能正常提供服務(wù)的,而在 D、E 分區(qū)因為不能得到大多數(shù)成員確認(rèn)(雖然分區(qū)了,但是因為配置的原因他們還是能知道所有的成員數(shù)量,比如 zk 集群啟動前需要配置所有成員地址,raft 也一樣),是不能進(jìn)行選舉的,所以保證只會有一個 leader。
如果分區(qū)為 A、B 和 C、D、E ,A、B 分區(qū)雖然 A 還是 leader,但是卻不能提供事務(wù)服務(wù)(setData),C、D、E 分區(qū)能重新選出 leader,還是能正常向外提供服務(wù)。
1)我們所說的日志(log)與狀態(tài)機(jī)(state machine)不是一回事,日志指還沒有提交到狀態(tài)機(jī)中的數(shù)據(jù)。
2)新 leader 永遠(yuǎn)不會通過計算副本數(shù)量提交舊日志,他只能復(fù)制舊日志都其他 follower 上,對于舊日志的提交,只能是新 leader 接收新的寫請求寫新日志,順帶著把舊日志提交了。
本文是Jason Wilder對于常見的服務(wù)發(fā)現(xiàn)項目 Zookeeper , Doozer , Etcd 所寫的一篇博客,其原文地址如下: Open-Source Service Discovery 。
服務(wù)發(fā)現(xiàn)是大多數(shù)分布式系統(tǒng)以及面向服務(wù)架構(gòu)(SOA)的一個核心組成部分。這個難題,簡單來說,可以認(rèn)為是:當(dāng)一項服務(wù)存在于多個主機(jī)節(jié)點上時,client端如何決策獲取相應(yīng)正確的IP和port。
在傳統(tǒng)情況下,當(dāng)出現(xiàn)服務(wù)存在于多個主機(jī)節(jié)點上時,都會使用靜態(tài)配置的方法來實現(xiàn)服務(wù)信息的注冊。但是當(dāng)大型系統(tǒng)中,需要部署更多服務(wù)的時候,事情就顯得復(fù)雜得多。在一個實時的系統(tǒng)中,由于自動或者人工的服務(wù)擴(kuò)展,或者服務(wù)的新添加部署,還有主機(jī)的宕機(jī)或者被替換,服務(wù)的location信息可能會很頻繁的變化。
在這樣的場景下,為了避免不必要的服務(wù)中斷,動態(tài)的服務(wù)注冊和發(fā)現(xiàn)就顯得尤為重要。
關(guān)于服務(wù)發(fā)現(xiàn)的話題,已經(jīng)很多次被人所提及,而且也的確不斷的在發(fā)展。現(xiàn)在,筆者介紹一下該領(lǐng)域內(nèi)一些open-source或者被經(jīng)常被世人廣泛討論的解決方案,嘗試?yán)斫馑鼈兊降资侨绾喂ぷ鞯摹L貏e的是,我們會較為專注于每一個解決方案的一致性算法,到底是強(qiáng)一致性,還是弱一致性;運行時依賴;client的集成選擇;以后最后這些特性的折中情況。
本文首先從幾個強(qiáng)一致性的項目于開始,比如Zookeeper,Doozer,Etcd,這些項目主要用于服務(wù)間的協(xié)調(diào),同時又可用于服務(wù)的注冊。
隨后,本文將討論一些在服務(wù)注冊以及發(fā)現(xiàn)方面比較有意思的項目,比如:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Serf,Spotify and DNS,最后是SkyDNS。
問題陳述
在定位服務(wù)的時候,其實會有兩個方面的問題:服務(wù)注冊(Service Registration)和服務(wù)發(fā)現(xiàn)(Service Discovery)。
服務(wù)注冊—— 一個服務(wù)將其位置信息在中心注冊節(jié)點注冊的過程。該服務(wù)一般會將它的主機(jī)IP地址以及端口號進(jìn)行注冊,有時也會有服務(wù)訪問的認(rèn)證信息,使用協(xié)議,版本號,以及關(guān)于環(huán)境的一些細(xì)節(jié)信息。
服務(wù)發(fā)現(xiàn)—— client端的應(yīng)用實例查詢中心注冊節(jié)點以獲知服務(wù)位置的過程。
每一個服務(wù)的服務(wù)注冊以及服務(wù)發(fā)現(xiàn),都需要考慮一些關(guān)于開發(fā)以及運營方面的問題:
監(jiān)控—— 當(dāng)一個已注冊完畢的服務(wù)失效的時候,如何處理。一些情況下,在一個設(shè)定的超時定時(timeout)后,該服務(wù)立即被一個其他的進(jìn)程在中心注冊節(jié)點處注銷。這種情況下,服務(wù)通常需要執(zhí)行一個心跳機(jī)制,來確保自身的存活狀態(tài);而客戶端必然需要能夠可靠處理失效的服務(wù)。
負(fù)載均衡—— 如果多個相同地位的服務(wù)都注冊完畢,如何在這些服務(wù)之間均衡所有client的請求負(fù)載?如果有一個master節(jié)點的話,是否可以正確處理client訪問的服務(wù)的位置。
集成方式—— 信息注冊節(jié)點是否需要提供一些語言綁定的支持,比如說,只支持Java?集成的過程是否需要將注冊過程以及發(fā)現(xiàn)過程的代碼嵌入到你的應(yīng)用程序中,或者使用一個類似于集成助手的進(jìn)程?
運行時依賴—— 是否需要JVM,ruby或者其他在你的環(huán)境中并不兼容的運行時?
可用性考慮—— 如果系統(tǒng)失去一個節(jié)點的話,是否還能正常工作?系統(tǒng)是否可以實時更新或升級,而不造成任何系統(tǒng)的癱瘓?既然集群的信息注冊節(jié)點是架構(gòu)中的中心部分,那該模塊是否會存在單點故障問題?
強(qiáng)一致性的Registries
首先介紹的三個服務(wù)注冊系統(tǒng)都采用了強(qiáng)一致性協(xié)議,實際上為達(dá)到通用的效果,使用了一致性的數(shù)據(jù)存儲。盡管我們把它們看作服務(wù)的注冊系統(tǒng),其實它們還可以用于協(xié)調(diào)服務(wù)來協(xié)助leader選舉,以及在一個分布式clients的集合中做centralized locking。
Zookeeper
Zookeeper是一個集中式的服務(wù),該服務(wù)可以維護(hù)服務(wù)配置信息,命名空間,提供分布式的同步,以及提供組化服務(wù)。Zookeeper是由Java語言實現(xiàn),實現(xiàn)了強(qiáng)一致性(CP),并且是使用 Zab協(xié)議 在ensemble集群之間協(xié)調(diào)服務(wù)信息的變化。
Zookeeper在ensemble集群中運行3個,5個或者7個成員。眾多client端為了可以訪問ensemble,需要使用綁定特定的語言。這種訪問形式被顯性的嵌入到了client的應(yīng)用實例以及服務(wù)中。
服務(wù)注冊的實現(xiàn)主要是通過命令空間(namespace)下的 ephemeral nodes 。ephemeral nodes只有在client建立連接后才存在。當(dāng)client所在節(jié)點啟動之后,該client端會使用一個后臺進(jìn)程獲取client的位置信息,并完成自身的注冊。如果該client失效或者失去連接的時候,該ephemeral node就從樹中消息。
服務(wù)發(fā)現(xiàn)是通過列舉以及查看具體服務(wù)的命名空間來完成的。Client端收到目前所有注冊服務(wù)的信息,無論一個服務(wù)是否不可用或者系統(tǒng)新添加了一個同類的服務(wù)。Client端同時也需要自行處理所有的負(fù)載均衡工作,以及服務(wù)的失效工作。
Zookeeper的API用起來可能并沒有那么方便,因為語言的綁定之間可能會造成一些細(xì)小的差異。如果使用的是基于JVM的語言的話, Curator Service Discovery Extension 可能會對你有幫助。
由于Zookeeper是一個CP強(qiáng)一致性的系統(tǒng),因此當(dāng)網(wǎng)絡(luò)分區(qū)(Partition)出故障的時候,你的部分系統(tǒng)可能將出出現(xiàn)不能注冊的情況,也可能出現(xiàn)不能找到已存在的注冊信息,即使它們可能在Partition出現(xiàn)期間仍然正常工作。特殊的是,在任何一個non-quorum端,任何讀寫都會返回一個錯誤信息。
Doozer
Doozer是一個一致的分布式數(shù)據(jù)存儲系統(tǒng),Go語言實現(xiàn),通過 Paxos算法 來實現(xiàn)共識的強(qiáng)一致性系統(tǒng)。這個項目開展了數(shù)年之后,停滯了一段時間,而且現(xiàn)在也關(guān)閉了一些fork數(shù),使得fork數(shù)降至160 。.不幸的是,現(xiàn)在很難知道該項目的實際發(fā)展?fàn)顟B(tài),以及它是否適合使用于生產(chǎn)環(huán)境。
Doozer在集群中運行3,5或者7個節(jié)點。和Zookeeper類似,Client端為了訪問集群,需要在自身的應(yīng)用或者服務(wù)中使用特殊的語言綁定。
Doozer的服務(wù)注冊就沒有Zookeeper這么直接,因為Doozer沒有那些ephemeral node的概念。一個服務(wù)可以在一條路徑下注冊自己,如果該服務(wù)不可用的話,它也不會自動地被移除。
現(xiàn)有很多種方式來解決這樣的問題。一個選擇是給注冊進(jìn)程添加一個時間戳和心跳機(jī)制,隨后在服務(wù)發(fā)現(xiàn)進(jìn)程中處理那些超時的路徑,也就是注冊的服務(wù)信息,當(dāng)然也可以通過另外一個清理進(jìn)程來實現(xiàn)。
服務(wù)發(fā)現(xiàn)和Zookeeper很類似,Doozer可以羅列出指定路徑下的所有入口,隨后可以等待該路徑下的任意改動。如果你在注冊期間使用一個時間戳和心跳,你就可以在服務(wù)發(fā)現(xiàn)期間忽略或者刪除任何過期的入口,也就是服務(wù)信息。
和Zookeeper一樣,Doozer是一個CP強(qiáng)一致性系統(tǒng),當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時,會導(dǎo)致同樣的后果。
Etcd
Etcd 是一個高可用的K-V存儲系統(tǒng),主要應(yīng)用于共享配置、服務(wù)發(fā)現(xiàn)等場景。Etcd可以說是被Zookeeper和Doozer催生而出。整個系統(tǒng)使用Go語言實現(xiàn),使用Raft算法來實現(xiàn)選舉一致,同時又具有一個基于HTTP+JSON的API。
Etcd,和Doozer和Zookeeper相似,通常在集群中運行3,5或者7個節(jié)點。client端可以使用一種特定的語言進(jìn)行綁定,同時也可以通過使用HTTP客戶端自行實現(xiàn)一種。
服務(wù)注冊環(huán)節(jié)主要依賴于使用一個key TTL來確保key的可用性,該key TTL會和服務(wù)端的心跳捆綁在一起。如果一個服務(wù)在更新key的TTL時失敗了,那么Etcd會對它進(jìn)行超時處理。如果一個服務(wù)變?yōu)椴豢捎脿顟B(tài),client會需要處理這樣的連接失效,然后嘗試另連接一個服務(wù)實例。
服務(wù)發(fā)現(xiàn)環(huán)節(jié)設(shè)計到羅列在一個目錄下的所有key值,隨后等待在該目錄上的所有變動信息。由于API接口是基于HTTP的,所以client應(yīng)用會的Etcd集群保持一個long-polling的連接。
由于Etcd使用 Raft一致性協(xié)議 ,故它應(yīng)該是一個強(qiáng)一致性系統(tǒng)。Raft需要一個leader被選舉,然后所有的client請求會被該leader所處理。然而,Etcd似乎也支持從non-leaders中進(jìn)行讀取信息,使用的方式是在讀情況下提高可用性的未公開的一致性參數(shù)。在網(wǎng)絡(luò)分區(qū)故障期間,寫操作還是會被leader處理,而且同樣會出現(xiàn)失效的情況。
很快,泥坑銀行就在回音山谷和海盜島建立了網(wǎng)點。這時候,兔小姐就對豬爸爸說到:『豬爸爸,現(xiàn)在我們已經(jīng)有三個銀行網(wǎng)點了,那么我們是不是可以允許客戶在三個地方都可以進(jìn)行交易呢?』
豬爸爸想了想,說到:『恐怕不行,兔小姐?!?/p>
兔小姐奇怪的回復(fù)到:『為什么?,客戶在任何地方交易,我們不是都可以先記錄下來,然后通知其他兩個地方,如果多數(shù)銀行都確認(rèn)了這筆交易,這不就行了嗎?』
『因為我們的交易記錄都是有唯一 ID,而且這個 ID 都是單調(diào)加 1 遞增的。假設(shè)現(xiàn)在我們的交易記錄 ID 是 10 了,如果我們允許在多個地方同時交易,譬如在泥坑小鎮(zhèn)和回音山谷,那么這次交易的記錄 ID 都是 11,這時候,海盜島就會收到兩個 ID 都是 11 的交易記錄,海盜島這邊沒法區(qū)分到底哪一個是正確的記錄了。』
『嗯,這么說起來倒是的,但我們現(xiàn)在可是在三個地方都建立了銀行網(wǎng)點,如果不使用,真的很可惜?!?/p>
『兔小姐,我們最開始在三個地方建立銀行網(wǎng)點的目的就是為了完全保證數(shù)據(jù)的安全性,也就是如果一個地方出現(xiàn)了問題,我們的系統(tǒng)仍然能夠正常的工作?!?/p>
『我明白了,豬爸爸。也就是說,我們的系統(tǒng)雖然部署在了三個地方,但同時只有一個能對外提供服務(wù)是吧?』
『是的,兔小姐。』
『那么,豬爸爸,我有個問題。我們是如何知道哪一個能對外提供服務(wù)呢?因為現(xiàn)在我們有三個網(wǎng)點,很有可能每個網(wǎng)點都認(rèn)為自己能對外提供服務(wù)了?!?/p>
『這是個好問題,我們需要有一套機(jī)制,能讓這三個網(wǎng)點自己選出一個 Leader 網(wǎng)點,對外提供服務(wù)。同時,如果這個 Leader 網(wǎng)點出現(xiàn)了故障,其他兩個網(wǎng)點能夠知道并再次選出一個 Leader 網(wǎng)點對外提供服務(wù)。』
豬爸爸接著道:『為了容易說明,我這里以我們實際的選舉為例吧。假設(shè)現(xiàn)在有三個成員,A,B 和 C,他們?nèi)藭嗷ネ镀边x出一個領(lǐng)導(dǎo)。這里我們先定義三種狀態(tài),Leader,Candiate 和 Follower。最開始三個人都是 Follower 狀態(tài),然后他們?nèi)绻麤Q定要選舉了,就變成 Candiate 狀態(tài),如果一個 Candiate 收到了大多數(shù)的選票,那么這個 Candiate 就變成了 Leader。而這時候其他的成員都重新變成 Follower,只有 Leader 能跟外部進(jìn)行交互。我這么解釋你大概能明白吧,兔小姐?』
兔小姐:『是的,豬爸爸,我大概能明白這三種狀態(tài),也就是只要一個網(wǎng)點成為了 Leader,這個網(wǎng)點才能對外提供服務(wù)吧。』
好吧,又輪到作者吐槽自己吐槽了,現(xiàn)實中銀行這么多個網(wǎng)點如果每次只能有一個主的銀行網(wǎng)點能對外提供服務(wù),那么這個銀行應(yīng)該會被客戶給投訴了。但這里我們就假設(shè)這樣吧。在 Raft 里面,每次只會有一個節(jié)點對外提供服務(wù),這個節(jié)點就是 Leader,而其他的節(jié)點就叫做 Follower。當(dāng)節(jié)點開始競選的時候,它們就會從 Follower 變成 Candidate。
豬爸爸喝了一口水,繼續(xù)說到:『是的,兔小姐。我們繼續(xù)以成員 A,B,C 為例。那我們現(xiàn)在要面臨的第一個問題,就是這三個成員如何選出一個 Leader?』
豬爸爸:『我們先假設(shè)三個成員地上都有很多小石子,但他們手上都只有 0 顆。只有某個成員碰到了一些事件,他才會從地上撿起一顆或者多顆石子。到底是什么事件,我們稍后再詳細(xì)解釋?!?/p>
豬爸爸接著道:『最開始,我們知道三個成員都是 Follower。然后,我們約定一個時間,譬如 10 分鐘之后吧,各個成員各自開始選舉,變成了 Candidate,同時,各自從地上撿起來一顆石子?!?/p>
兔小姐:『看來,這就是你上面說的事件,也就是當(dāng)一個 Follower 變成 Candidate 的時候,也就是自己開始新一輪選舉的時候,就給自己加一顆石子吧?!?/p>
『非常正確,兔小姐。』然后豬爸爸接著說道:『 Candidate 會先給自己投一票,然后會給其他的幾個成員發(fā)送投票信息,讓它們選舉自己成為 Leader。如果一個 Candidate 知道自己已經(jīng)得到了大多數(shù)的選票,那么就能成為 Leader 了?!?/p>
『獲得大部分投票就成為 Leader 這個就跟我們自己的選舉一樣的。但是,豬爸爸,自己給自己投一票我能理解,但其他人為什么要給我投票呢?』
『這是個好問題,兔小姐,你還記得我前面說的石子吧?』
『當(dāng)然記得,豬爸爸,當(dāng)開始選舉的時候,就給自己加一顆石子。』
『是的,兔小姐。首先我們來考慮初始情況,這時候我們還沒進(jìn)行任何交易,交易記錄還是 0。當(dāng)一個 Candidate 給其他成員發(fā)送投票消息的時候,會帶上自己的石子數(shù)量。當(dāng)其他成員收到投票消息,如果發(fā)現(xiàn)自己的石子數(shù)量比收到的投票信息消息的石子數(shù)量要少,就給這個 Candidate 投票,同時自己變成跟隨者,從地上撿起來足夠多的石子直到自己的石子數(shù)量跟投票消息里面的一樣?!?/p>
『如果自己的石子數(shù)量比投票消息的石子數(shù)量要多呢,我們?nèi)绾翁幚恚俊?/p>
『兔小姐,如果是這樣,那么就直接丟棄這條消息。我們通常不管石子數(shù)量比自己當(dāng)前手上石子數(shù)量少的消息?!?/p>
『這主意不錯,如果自己手上的石子數(shù)量跟投票消息里面的一樣呢,我們又如何處理?』
『這個就要看自己是不是已經(jīng)給其他人或者自己投票了,如果我已經(jīng)給其他人或者自己投票了,我就給你回復(fù)一條拒絕消息。』
『我討厭被拒絕。』兔小姐很傷心的說道,不過我想兔先生可不敢拒絕她。
『沒辦法,為了保證選舉的安全,我們必須這樣。所以對于一輪選舉來說,它可能會碰到三種情況,自己變成了 Leader,其它節(jié)點變成了 Leader,以及沒有選出 Leader。』
『為什么會沒選出 Leader 呢?』
『考慮到這種情況,三個成員 A,B,C ,最開始石子數(shù)量都是 0,然后他們同時開始選舉,先都給自己投了一票,這樣所有人的石子數(shù)量現(xiàn)在都是 1,所以無論誰收到其他人的消息,都會回復(fù)拒絕,這樣我們必須開始下一輪選舉』
『但下一輪選舉也可能沒有選出來吧?!?/p>
『是的,兔小姐,你還記得我之前提到的 10 分鐘吧。』
『記得,10 分鐘之后,都開始選舉?!?/p>
『如果每次大家重新開始選舉的時間都是一樣,很有可能都選不出來。所以我們可以約定一個時間范圍,譬如 10 到 20 分鐘,各個成員會隨機(jī)在這個時間段里面選一個時間來等待,這樣就能錯開選舉了?!?/p>
『這主意不錯,這樣選出來 Leader 的概率就大了很多?!?/p>
好了,說了這么多,該說說實際的 Raft 了。上面的石子數(shù)量就是 Raft 里面的 Term。每次開始一輪新的選舉,Term 就加 1。如果選出了 Leader,那么就會 一直維持這個 Term,直到下一次選舉。上面的 10 分鐘就是 election timeout。
在實際 Raft 中,還有一些復(fù)雜的 corner case,譬如如果選出了 Leader,但另一個 Candidate 有更高的 Term,這樣很可能會讓 Leader 變成 Follower,或者讓 Follower 給這個更高 Term 的 Candidate 重新投票。為了解決這樣的問題,我們可以考慮 Pre-Vote,也就是一個 Follower 如果要開始投票,它并不會立刻變成 Candidate,給自己 Term + 1,而是會先變成 Pre-Candidate 的狀態(tài),用當(dāng)前的 Term 去問其他的節(jié)點能否給自己投票,如果收到了大多數(shù)的同意消息,那么才會變成 Candidate 繼續(xù)后面的選舉流程。
另外,我們也可以考慮 Check Quorum,當(dāng)一個 Follower 收到了更高的 Term 選舉消息,如果它確信當(dāng)前集群還在正在工作,也就是在 election timeout 的時間里面仍然收到了 Leader 的消息,那么這個 Follower 會直接丟掉這個消息。
這里我們只討論了 Log 都沒有的情況,對于已經(jīng)有 Log 的情況,選舉情況還要做一些其它判斷,我們后續(xù)再說明。
豬爸爸休息了下,繼續(xù)說道:『現(xiàn)在我們解決了第一個問題,就是如何選出一個 Leader。下面,我們就要面臨兩個問題。一個是如何讓 Leader 一直能正常工作。另一個就是萬一 Leader 出現(xiàn)了問題,其它的 Follower 如何知道,并開始重新選舉?』
『這看起來有點復(fù)雜?!?/p>
『其實一點也不,兔小姐。當(dāng)我們選出來 Leader 之后,Leader 就會就會開始處理外面的請求。你還記得我前面說的我們的安全交易模型吧?必須大多數(shù)節(jié)點都記錄了這筆交易,我們才能實際交易?!?/p>
『當(dāng)然記得,豬爸爸?!?/p>
『因為現(xiàn)在只有 Leader 能處理交易,所以每筆交易,Leader 都要發(fā)給 Follower,這樣 Leader 和 Follower 之間就有了通信。所以只要我們一直有交易處理,這條通信鏈條就不會斷掉,Leader 就一直能維持自己是 Leader 的狀態(tài)了?!?/p>
『嗯,是這樣的。但如果到了晚上,沒有交易,怎么辦呢?』
『這是個好問題,兔小姐。所以 Leader 會定期給 Follower 發(fā)送一條消息,說我現(xiàn)在還是 Leader,這個消息其實就是跟上面說的發(fā)送交易記錄的作用一樣的,只是讓 Follower 知道 Leader 還在就可以了?!?/p>
『這主意不錯,那如果很長一段時間 Follower 沒收到 Leader 發(fā)過來的消息,那我們怎么辦呢?』
『還記得我前面說的 10 分鐘吧,我們可以繼續(xù)約定,如果 10 分鐘內(nèi),F(xiàn)ollower 沒有收到 Leader 的任何消息,那么 Follower 就認(rèn)為 Leader 有問題了,這樣 Follower 就開始重新選舉?!?/p>
在 Raft 里面,如果選出來一個 Leader,Leader 會定期給 Follower 發(fā)送心跳,這個定期的時間我們通常叫做 heartbeat timeout,如果 Follower 在 election timeout 的時間里都沒收到 Leader 的消息,就開始新一輪的選舉。Heartbeat timeout 的時間要比 election timeout 小很多,譬如 election timeout 如果是 10s,那么 heartbeat timeout 可能就是 2s 或者 3s。
兔小姐聽完了豬爸爸的回答,仔細(xì)想了想,說道『豬爸爸,你前面說的重新選舉,貌似假設(shè)的是初始情況,沒有任何交易記錄的情況。但我們選出了 Leader,這時候可能進(jìn)行了很多交易了,那么這時候 Follower 再選舉 Leader 還有啥需要注意的呢?』
『這個問題非常的好,兔小姐!』豬爸爸由衷的贊嘆道?!褐埃覀儍H僅是通過石子數(shù)量來決定是否成為 Leader,但這樣是遠(yuǎn)遠(yuǎn)不夠的,我們還必須通過各自交易記錄的數(shù)量來最終確定是否能成為 Leader。』
『要通過交易記錄數(shù)量來確定?』兔小姐不解的問道。
『是的,兔小姐。你還記得最開始我說的交易記錄的特性吧。每次交易,我們都會使用一個遞增的唯一 ID 來標(biāo)識記錄?!?/p>
『我當(dāng)然記得,豬爸爸』
『現(xiàn)在我們回到銀行網(wǎng)點這邊,假設(shè)它們之間選出泥坑小鎮(zhèn)作為了 Leader,那么客戶就能在泥坑小鎮(zhèn)進(jìn)行交易了。一段時間之后,泥坑小鎮(zhèn) 這邊進(jìn)行了 10 次交易,也就是最后一次交易記錄 ID 是 10。因為一次交易必須大部分銀行網(wǎng)點都確定收到了這次交易記錄,所以一定有一個網(wǎng)點交易數(shù)量是跟泥坑小鎮(zhèn) 一樣的,假設(shè)這里是回音山谷。而海盜島可能稍微落后了,只有 9 條,最后一次交易記錄是 9。如果只按照我們先前的石子數(shù)量來判斷,如果海盜島的石子數(shù)量最多,那海盜島在泥坑小鎮(zhèn)出現(xiàn)了問題之后,就會被選出 Leader,但實際是不允許的。因為只有回音山谷有最新的交易數(shù)據(jù),但海盜島沒有?!?/p>
『雖然很繞,但我大概有點明白了,豬爸爸。也就是說,我們選舉的時候,還需要判斷,被選舉者是否有最新的交易記錄吧。』
『是的,兔小姐,所以 Candidate 在發(fā)送投票消息的時候,不光要帶上石子數(shù)量,還需要帶上自己最后一條交易記錄的 ID。如果我收到了一條投票消息,發(fā)現(xiàn)這條消息里面的交易記錄 ID 比我當(dāng)前的還要大或者相等,那我就能認(rèn)為發(fā)送這條投票消息的人有比我更多的交易記錄,我就可以給他投票?!?/p>
在實際 Raft 里面,Candidate 給其他節(jié)點發(fā)送投票消息的時候,會帶上自己當(dāng)前最后一條 Log 的 Term 和 Index。如果投票消息的 Term 比自己最后一條 Log 的 Term 大,或者兩個 Term 相等,但投票消息的 Index 大于或者等于自己最后一條 Log 的 Index,那么就可以給這個 Candidate 投票。至于為什么要同時判斷 Term 和 Index,我們可以考慮一個 corner case,假設(shè)有 A,B,C 三個節(jié)點。
在 5 的時候,A 也有一個 Log 100,但這個 Log 其實是不正確的,所以 B 和 C 必須拒絕給 A 投票,所以這里我們要借助 Term。因為 A 寫 Log 100 的時候,Term 還是 10,但 B 和 C 這時候?qū)懙?Log 100 里面的 Term 已經(jīng)是 11 了,所以它們就會知道 A 并沒有最新的 Log。
好了,扯了這么多,都無非是說的 Raft 里面的 Leader Election,雖然 Raft 的 Leader Election 原理很好理解,主要還是一些 corner case 的問題,以及出現(xiàn)了一個壞的節(jié)點,如果讓它別亂發(fā)投票消息,影響到整個集群。
java 是什么語言寫的
JAVA中就虛擬機(jī)是其它語言開發(fā)的,用的是C語言+匯編語言 基于此之上就是JAVA本身了 虛擬機(jī)只起到解析作用
另外,JAVA并不比C語言慢,說JAVA慢一般是九十年代那時候的JAVA, 而現(xiàn)在 在一段優(yōu)秀的JAVA程序和C程序執(zhí)行效率上來比較是沒有多大差距的 并且現(xiàn)在JAVA已經(jīng)可以像C語言那樣,直接編譯為可執(zhí)行文件(不用虛擬機(jī),跨平臺為代價)了
不知道你看過 卓越編程之道二(運用底層思維編寫高級代碼) 沒有,那里面詳細(xì)的講述了高級語言從編寫到編譯執(zhí)行的過程,通過目標(biāo)文件的反匯編對比,發(fā)現(xiàn)C,C++,JAVA,dephi等語言在同等質(zhì)量下的目標(biāo)文件長度上基本上沒多大區(qū)別,一門語言的運行速度快慢,與你編寫代碼過程中是否符合編譯器規(guī)則息息相關(guān)。 有空你可以去看看這本書。
glusterfs 是什么語言寫的
glusterfs 是什么語言寫的
使用opencv需要編譯源碼,得到庫文件??梢杂胏make構(gòu)建項目后編譯,也可以直接用官方提供的編譯好的版本。
官方提供的編譯庫一般只是標(biāo)準(zhǔn)版本,沒有附加某些庫,比如tbb等,要想讓opencv使用tbb等庫,就只能自己構(gòu)建項目后編譯。
當(dāng)然,一般使用的話,用官方提供的庫即可。OpenCV2.3.1版本就提供編譯好的庫,可以直接設(shè)置使用。
bigtable是什么語言寫的
不過有人大費周折為他建立了一個類似于“關(guān)于 Chuck Norris 的事實”這樣的網(wǎng)站,這倒是件不同尋常的事。這是因為 Jeff Dean 是一位軟件工程師
zookeeper是什么語言寫的
本文是Jason Wilder對于常見的服務(wù)發(fā)現(xiàn)項目 Zookeeper , Doozer , Etcd 所寫的一篇博客,其原文地址如下: Open-Source Service Discovery 。
服務(wù)發(fā)現(xiàn)是大多數(shù)分布式系統(tǒng)以及面向服務(wù)架構(gòu)(SOA)的一個核心組成部分。這個難題,簡單來說,可以認(rèn)為是:當(dāng)一項服務(wù)存在于多個主機(jī)節(jié)點上時,client端如何決策獲取相應(yīng)正確的IP和port。
在傳統(tǒng)情況下,當(dāng)出現(xiàn)服務(wù)存在于多個主機(jī)節(jié)點上時,都會使用靜態(tài)配置的方法來實現(xiàn)服務(wù)信息的注冊。但是當(dāng)大型系統(tǒng)中,需要部署更多服務(wù)的時候,事情就顯得復(fù)雜得多。在一個實時的系統(tǒng)中,由于自動或者人工的服務(wù)擴(kuò)展,或者服務(wù)的新添加部署,還有主機(jī)的宕機(jī)或者被替換,服務(wù)的location信息可能會很頻繁的變化。
在這樣的場景下,為了避免不必要的服務(wù)中斷,動態(tài)的服務(wù)注冊和發(fā)現(xiàn)就顯得尤為重要。
關(guān)于服務(wù)發(fā)現(xiàn)的話題,已經(jīng)很多次被人所提及,而且也的確不斷的在發(fā)展?,F(xiàn)在,筆者介紹一下該領(lǐng)域內(nèi)一些open-source或者被經(jīng)常被世人廣泛討論的解決方案,嘗試?yán)斫馑鼈兊降资侨绾喂ぷ鞯?。特別的是,我們會較為專注于每一個解決方案的一致性算法,到底是強(qiáng)一致性,還是弱一致性;運行時依賴;client的集成選擇;以后最后這些特性的折中情況。
本文首先從幾個強(qiáng)一致性的項目于開始,比如Zookeeper,Doozer,Etcd,這些項目主要用于服務(wù)間的協(xié)調(diào),同時又可用于服務(wù)的注冊。
隨后,本文將討論一些在服務(wù)注冊以及發(fā)現(xiàn)方面比較有意思的項目,比如:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Serf,Spotify and DNS,最后是SkyDNS。
問題陳述
在定位服務(wù)的時候,其實會有兩個方面的問題:服務(wù)注冊(Service Registration)和服務(wù)發(fā)現(xiàn)(Service Discovery)。
服務(wù)注冊—— 一個服務(wù)將其位置信息在中心注冊節(jié)點注冊的過程。該服務(wù)一般會將它的主機(jī)IP地址以及端口號進(jìn)行注冊,有時也會有服務(wù)訪問的認(rèn)證信息,使用協(xié)議,版本號,以及關(guān)于環(huán)境的一些細(xì)節(jié)信息。
服務(wù)發(fā)現(xiàn)—— client端的應(yīng)用實例查詢中心注冊節(jié)點以獲知服務(wù)位置的過程。
每一個服務(wù)的服務(wù)注冊以及服務(wù)發(fā)現(xiàn),都需要考慮一些關(guān)于開發(fā)以及運營方面的問題:
監(jiān)控—— 當(dāng)一個已注冊完畢的服務(wù)失效的時候,如何處理。一些情況下,在一個設(shè)定的超時定時(timeout)后,該服務(wù)立即被一個其他的進(jìn)程在中心注冊節(jié)點處注銷。這種情況下,服務(wù)通常需要執(zhí)行一個心跳機(jī)制,來確保自身的存活狀態(tài);而客戶端必然需要能夠可靠處理失效的服務(wù)。
負(fù)載均衡—— 如果多個相同地位的服務(wù)都注冊完畢,如何在這些服務(wù)之間均衡所有client的請求負(fù)載?如果有一個master節(jié)點的話,是否可以正確處理client訪問的服務(wù)的位置。
集成方式—— 信息注冊節(jié)點是否需要提供一些語言綁定的支持,比如說,只支持Java?集成的過程是否需要將注冊過程以及發(fā)現(xiàn)過程的代碼嵌入到你的應(yīng)用程序中,或者使用一個類似于集成助手的進(jìn)程?
運行時依賴—— 是否需要JVM,ruby或者其他在你的環(huán)境中并不兼容的運行時?
可用性考慮—— 如果系統(tǒng)失去一個節(jié)點的話,是否還能正常工作?系統(tǒng)是否可以實時更新或升級,而不造成任何系統(tǒng)的癱瘓?既然集群的信息注冊節(jié)點是架構(gòu)中的中心部分,那該模塊是否會存在單點故障問題?
強(qiáng)一致性的Registries
首先介紹的三個服務(wù)注冊系統(tǒng)都采用了強(qiáng)一致性協(xié)議,實際上為達(dá)到通用的效果,使用了一致性的數(shù)據(jù)存儲。盡管我們把它們看作服務(wù)的注冊系統(tǒng),其實它們還可以用于協(xié)調(diào)服務(wù)來協(xié)助leader選舉,以及在一個分布式clients的集合中做centralized locking。
Zookeeper
Zookeeper是一個集中式的服務(wù),該服務(wù)可以維護(hù)服務(wù)配置信息,命名空間,提供分布式的同步,以及提供組化服務(wù)。Zookeeper是由Java語言實現(xiàn),實現(xiàn)了強(qiáng)一致性(CP),并且是使用 Zab協(xié)議 在ensemble集群之間協(xié)調(diào)服務(wù)信息的變化。
Zookeeper在ensemble集群中運行3個,5個或者7個成員。眾多client端為了可以訪問ensemble,需要使用綁定特定的語言。這種訪問形式被顯性的嵌入到了client的應(yīng)用實例以及服務(wù)中。
服務(wù)注冊的實現(xiàn)主要是通過命令空間(namespace)下的 ephemeral nodes 。ephemeral nodes只有在client建立連接后才存在。當(dāng)client所在節(jié)點啟動之后,該client端會使用一個后臺進(jìn)程獲取client的位置信息,并完成自身的注冊。如果該client失效或者失去連接的時候,該ephemeral node就從樹中消息。
服務(wù)發(fā)現(xiàn)是通過列舉以及查看具體服務(wù)的命名空間來完成的。Client端收到目前所有注冊服務(wù)的信息,無論一個服務(wù)是否不可用或者系統(tǒng)新添加了一個同類的服務(wù)。Client端同時也需要自行處理所有的負(fù)載均衡工作,以及服務(wù)的失效工作。
Zookeeper的API用起來可能并沒有那么方便,因為語言的綁定之間可能會造成一些細(xì)小的差異。如果使用的是基于JVM的語言的話, Curator Service Discovery Extension 可能會對你有幫助。
由于Zookeeper是一個CP強(qiáng)一致性的系統(tǒng),因此當(dāng)網(wǎng)絡(luò)分區(qū)(Partition)出故障的時候,你的部分系統(tǒng)可能將出出現(xiàn)不能注冊的情況,也可能出現(xiàn)不能找到已存在的注冊信息,即使它們可能在Partition出現(xiàn)期間仍然正常工作。特殊的是,在任何一個non-quorum端,任何讀寫都會返回一個錯誤信息。
Doozer
Doozer是一個一致的分布式數(shù)據(jù)存儲系統(tǒng),Go語言實現(xiàn),通過 Paxos算法 來實現(xiàn)共識的強(qiáng)一致性系統(tǒng)。這個項目開展了數(shù)年之后,停滯了一段時間,而且現(xiàn)在也關(guān)閉了一些fork數(shù),使得fork數(shù)降至160 。.不幸的是,現(xiàn)在很難知道該項目的實際發(fā)展?fàn)顟B(tài),以及它是否適合使用于生產(chǎn)環(huán)境。
Doozer在集群中運行3,5或者7個節(jié)點。和Zookeeper類似,Client端為了訪問集群,需要在自身的應(yīng)用或者服務(wù)中使用特殊的語言綁定。
Doozer的服務(wù)注冊就沒有Zookeeper這么直接,因為Doozer沒有那些ephemeral node的概念。一個服務(wù)可以在一條路徑下注冊自己,如果該服務(wù)不可用的話,它也不會自動地被移除。
現(xiàn)有很多種方式來解決這樣的問題。一個選擇是給注冊進(jìn)程添加一個時間戳和心跳機(jī)制,隨后在服務(wù)發(fā)現(xiàn)進(jìn)程中處理那些超時的路徑,也就是注冊的服務(wù)信息,當(dāng)然也可以通過另外一個清理進(jìn)程來實現(xiàn)。
服務(wù)發(fā)現(xiàn)和Zookeeper很類似,Doozer可以羅列出指定路徑下的所有入口,隨后可以等待該路徑下的任意改動。如果你在注冊期間使用一個時間戳和心跳,你就可以在服務(wù)發(fā)現(xiàn)期間忽略或者刪除任何過期的入口,也就是服務(wù)信息。
和Zookeeper一樣,Doozer是一個CP強(qiáng)一致性系統(tǒng),當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時,會導(dǎo)致同樣的后果。
Etcd
Etcd 是一個高可用的K-V存儲系統(tǒng),主要應(yīng)用于共享配置、服務(wù)發(fā)現(xiàn)等場景。Etcd可以說是被Zookeeper和Doozer催生而出。整個系統(tǒng)使用Go語言實現(xiàn),使用Raft算法來實現(xiàn)選舉一致,同時又具有一個基于HTTP+JSON的API。
Etcd,和Doozer和Zookeeper相似,通常在集群中運行3,5或者7個節(jié)點。client端可以使用一種特定的語言進(jìn)行綁定,同時也可以通過使用HTTP客戶端自行實現(xiàn)一種。
服務(wù)注冊環(huán)節(jié)主要依賴于使用一個key TTL來確保key的可用性,該key TTL會和服務(wù)端的心跳捆綁在一起。如果一個服務(wù)在更新key的TTL時失敗了,那么Etcd會對它進(jìn)行超時處理。如果一個服務(wù)變?yōu)椴豢捎脿顟B(tài),client會需要處理這樣的連接失效,然后嘗試另連接一個服務(wù)實例。
服務(wù)發(fā)現(xiàn)環(huán)節(jié)設(shè)計到羅列在一個目錄下的所有key值,隨后等待在該目錄上的所有變動信息。由于API接口是基于HTTP的,所以client應(yīng)用會的Etcd集群保持一個long-polling的連接。
由于Etcd使用 Raft一致性協(xié)議 ,故它應(yīng)該是一個強(qiáng)一致性系統(tǒng)。Raft需要一個leader被選舉,然后所有的client請求會被該leader所處理。然而,Etcd似乎也支持從non-leaders中進(jìn)行讀取信息,使用的方式是在讀情況下提高可用性的未公開的一致性參數(shù)。在網(wǎng)絡(luò)分區(qū)故障期間,寫操作還是會被leader處理,而且同樣會出現(xiàn)失效的情況。
delphi是什么語言寫的
Object Pascal
jdk是什么語言寫的
你猜~~
druid是什么語言寫的
Druid是Java語言中最好的數(shù)據(jù)庫連接池。Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能。
EPM和BI是什么???是用java語言寫的嗎?
不全是java,epm有用.寫的,但是絕大多少是用java。
BI設(shè)計到大數(shù)據(jù),除了java還有一些新技術(shù),比如scala和kalfka。
dos 是什么語言寫的?
匯編語言,低級編輯語言
Api 是什么語言寫的?
API肯定也是一種語言實現(xiàn)他具體功能的啊 .
相當(dāng)于函數(shù),不過我們只能調(diào)用,不知道如何實現(xiàn)的.
本文是JasonWilder對于常見的服務(wù)發(fā)現(xiàn)項目Zookeeper,Doozer,Etcd所寫的一篇博客,其原文地址如下:Open-SourceServiceDiscovery。服務(wù)發(fā)現(xiàn)是大多數(shù)分布式系統(tǒng)以及面向服務(wù)架構(gòu)(SOA)的一個核心組成部分。這個難題,簡單來說,可以認(rèn)為是:當(dāng)一項服務(wù)存在于多個主機(jī)節(jié)點上時,client端如何決策獲取相應(yīng)正確的IP和port。在傳統(tǒng)情況下,當(dāng)出現(xiàn)服務(wù)存在于多個主機(jī)節(jié)點上時,都會使用靜態(tài)配置的方法來實現(xiàn)服務(wù)信息的注冊。但是當(dāng)大型系統(tǒng)中,需要部署服務(wù)的時候,事情就顯得復(fù)雜得多。在一個實時的系統(tǒng)中,由于自動或者人工的服務(wù)擴(kuò)展,或者服務(wù)的新添加部署,還有主機(jī)的宕機(jī)或者被替換,服務(wù)的location信息可能會很頻繁的變化。在這樣的場景下,為了避免不必要的服務(wù)中斷,動態(tài)的服務(wù)注冊和發(fā)現(xiàn)就顯得尤為重要。關(guān)于服務(wù)發(fā)現(xiàn)的話題,已經(jīng)很多次被人所提及,而且也的確不斷的在發(fā)展?,F(xiàn)在,筆者介紹一下該領(lǐng)域內(nèi)一些open-source或者被經(jīng)常被世人廣泛討論的解決方案,嘗試?yán)斫馑鼈兊降资侨绾喂ぷ鞯?。特別的是,我們會較為專注于每一個解決方案的一致性算法,到底是強(qiáng)一致性,還是弱一致性;運行時依賴;client的集成選擇;以后最后這些特性的折中情況。本文首先從幾個強(qiáng)一致性的項目于開始,比如Zookeeper,Doozer,Etcd,這些項目主要用于服務(wù)間的協(xié)調(diào),同時又可用于服務(wù)的注冊。隨后,本文將討論一些在服務(wù)注冊以及發(fā)現(xiàn)方面比較有意思的項目,比如:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Serf,SpotifyandDNS,最后是SkyDNS。問題陳述在定位服務(wù)的時候,其實會有兩個方面的問題:服務(wù)注冊(ServiceRegistration)和服務(wù)發(fā)現(xiàn)(ServiceDiscovery)。服務(wù)注冊——一個服務(wù)將其位置信息在中心注冊節(jié)點注冊的過程。該服務(wù)一般會將它的主機(jī)IP地址以及端口號進(jìn)行注冊,有時也會有服務(wù)訪問的認(rèn)證信息,使用協(xié)議,版本號,以及關(guān)于環(huán)境的一些細(xì)節(jié)信息。服務(wù)發(fā)現(xiàn)——client端的應(yīng)用實例查詢中心注冊節(jié)點以獲知服務(wù)位置的過程。每一個服務(wù)的服務(wù)注冊以及服務(wù)發(fā)現(xiàn),都需要考慮一些關(guān)于開發(fā)以及運營方面的問題:監(jiān)控——當(dāng)一個已注冊完畢的服務(wù)失效的時候,如何處理。一些情況下,在一個設(shè)定的超時定時(timeout)后,該服務(wù)立即被一個其他的進(jìn)程在中心注冊節(jié)點處注銷。這種情況下,服務(wù)通常需要執(zhí)行一個心跳機(jī)制,來確保自身的存活狀態(tài);而客戶端必然需要能夠可靠處理失效的服務(wù)。負(fù)載均衡——如果多個相同地位的服務(wù)都注冊完畢,如何在這些服務(wù)之間均衡所有client的請求負(fù)載?如果有一個master節(jié)點的話,是否可以正確處理client訪問的服務(wù)的位置。集成方式——信息注冊節(jié)點是否需要提供一些語言綁定的支持,比如說,只支持Java?集成的過程是否需要將注冊過程以及發(fā)現(xiàn)過程的代碼嵌入到你的應(yīng)用程序中,或者使用一個類似于集成助手的進(jìn)程?運行時依賴——是否需要JVM,ruby或者其他在你的環(huán)境中并不兼容的運行時?可用性考慮——如果系統(tǒng)失去一個節(jié)點的話,是否還能正常工作?系統(tǒng)是否可以實時更新或升級,而不造成任何系統(tǒng)的癱瘓?既然集群的信息注冊節(jié)點是架構(gòu)中的中心部分,那該模塊是否會存在單點故障問題?強(qiáng)一致性的Registries首先介紹的三個服務(wù)注冊系統(tǒng)都采用了強(qiáng)一致性協(xié)議,實際上為達(dá)到通用的效果,使用了一致性的數(shù)據(jù)存儲。盡管我們把它們看作服務(wù)的注冊系統(tǒng),其實它們還可以用于協(xié)調(diào)服務(wù)來協(xié)助leader選舉,以及在一個分布式clients的集合中做centralizedlocking。ZookeeperZookeeper是一個集中式的服務(wù),該服務(wù)可以維護(hù)服務(wù)配置信息,命名空間,提供分布式的同步,以及提供組化服務(wù)。Zookeeper是由Java語言實現(xiàn),實現(xiàn)了強(qiáng)一致性(CP),并且是使用Zab協(xié)議在ensemble集群之間協(xié)調(diào)服務(wù)信息的變化。Zookeeper在ensemble集群中運行3個,5個或者7個成員。眾多client端為了可以訪問ensemble,需要使用綁定特定的語言。這種訪問形式被顯性的嵌入到了client的應(yīng)用實例以及服務(wù)中。服務(wù)注冊的實現(xiàn)主要是通過命令空間(namespace)下的ephemeralnodes。ephemeralnodes只有在client建立連接后才存在。當(dāng)client所在節(jié)點啟動之后,該client端會使用一個后臺進(jìn)程獲取client的位置信息,并完成自身的注冊。如果該client失效或者失去連接的時候,該ephemeralnode就從樹中消息。服務(wù)發(fā)現(xiàn)是通過列舉以及查看具體服務(wù)的命名空間來完成的。Client端收到目前所有注冊服務(wù)的信息,無論一個服務(wù)是否不可用或者系統(tǒng)新添加了一個同類的服務(wù)。Client端同時也需要自行處理所有的負(fù)載均衡工作,以及服務(wù)的失效工作。Zookeeper的API用起來可能并沒有那么方便,因為語言的綁定之間可能會造成一些細(xì)小的差異。如果使用的是基于JVM的語言的話,CuratorServiceDiscoveryExtension可能會對你有幫助。由于Zookeeper是一個CP強(qiáng)一致性的系統(tǒng),因此當(dāng)網(wǎng)絡(luò)分區(qū)(Partition)出故障的時候,你的部分系統(tǒng)可能將出出現(xiàn)不能注冊的情況,也可能出現(xiàn)不能找到已存在的注冊信息,即使它們可能在Partition出現(xiàn)期間仍然正常工作。特殊的是,在任何一個non-quorum端,任何讀寫都會返回一個錯誤信息。DoozerDoozer是一個一致的分布式數(shù)據(jù)存儲系統(tǒng),Go語言實現(xiàn),通過Paxos算法來實現(xiàn)共識的強(qiáng)一致性系統(tǒng)。這個項目開展了數(shù)年之后,停滯了一段時間,而且現(xiàn)在也關(guān)閉了一些fork數(shù),使得fork數(shù)降至160。.不幸的是,現(xiàn)在很難知道該項目的實際發(fā)展?fàn)顟B(tài),以及它是否適合使用于生產(chǎn)環(huán)境。Doozer在集群中運行3,5或者7個節(jié)點。和Zookeeper類似,Client端為了訪問集群,需要在自身的應(yīng)用或者服務(wù)中使用特殊的語言綁定。Doozer的服務(wù)注冊就沒有Zookeeper這么直接,因為Doozer沒有那些ephemeralnode的概念。一個服務(wù)可以在一條路徑下注冊自己,如果該服務(wù)不可用的話,它也不會自動地被移除?,F(xiàn)有很多種方式來解決這樣的問題。一個選擇是給注冊進(jìn)程添加一個時間戳和心跳機(jī)制,隨后在服務(wù)發(fā)現(xiàn)進(jìn)程中處理那些超時的路徑,也就是注冊的服務(wù)信息,當(dāng)然也可以通過另外一個清理進(jìn)程來實現(xiàn)。服務(wù)發(fā)現(xiàn)和Zookeeper很類似,Doozer可以羅列出指定路徑下的所有入口,隨后可以等待該路徑下的任意改動。如果你在注冊期間使用一個時間戳和心跳,你就可以在服務(wù)發(fā)現(xiàn)期間忽略或者刪除任何過期的入口,也就是服務(wù)信息。和Zookeeper一樣,Doozer是一個CP強(qiáng)一致性系統(tǒng),當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時,會導(dǎo)致同樣的后果。EtcdEtcd是一個高可用的K-V存儲系統(tǒng),主要應(yīng)用于共享配置、服務(wù)發(fā)現(xiàn)等場景。Etcd可以說是被Zookeeper和Doozer催生而出。整個系統(tǒng)使用Go語言實現(xiàn),使用Raft算法來實現(xiàn)選舉一致,同時又具有一個基于HTTP+JSON的API。Etcd,和Doozer和Zookeeper相似,通常在集群中運行3,5或者7個節(jié)點。client端可以使用一種特定的語言進(jìn)行綁定,同時也可以通過使用HTTP客戶端自行實現(xiàn)一種。服務(wù)注冊環(huán)節(jié)主要依賴于使用一個keyTTL來確保key的可用性,該keyTTL會和服務(wù)端的心跳捆綁在一起。如果一個服務(wù)在更新key的TTL時失敗了,那么Etcd會對它進(jìn)行超時處理。如果一個服務(wù)變?yōu)椴豢捎脿顟B(tài),client會需要處理這樣的連接失效,然后嘗試另連接一個服務(wù)實例。服務(wù)發(fā)現(xiàn)環(huán)節(jié)設(shè)計到羅列在一個目錄下的所有key值,隨后等待在該目錄上的所有變動信息。由于API接口是基于HTTP的,所以client應(yīng)用會的Etcd集群保持一個long-polling的連接。由于Etcd使用Raft一致性協(xié)議,故它應(yīng)該是一個強(qiáng)一致性系統(tǒng)。Raft需要一個leader被選舉,然后所有的client請求會被該leader所處理。然而,Etcd似乎也支持從non-leaders中進(jìn)行讀取信息,使用的方式是在讀情況下提高可用性的未公開的一致性參數(shù)。在網(wǎng)絡(luò)分區(qū)故障期間,寫操作還是會被leader處理,而且同樣會出現(xiàn)失效的情況。