课时 16 深入理解 etcd:基于原理解析(曾凡松)
本文將主要分享以下三方面的內(nèi)容:
etcd 項目的發(fā)展歷程
etcd 誕生于 CoreOS 公司,它最初是用于解決集群管理系統(tǒng)中 OS 升級的分布式并發(fā)控制以及配置文件的存儲與分發(fā)等問題。基于此,etcd 被設(shè)計為提供高可用、強一致的小型 keyvalue 數(shù)據(jù)存儲服務(wù)。
項目當前隸屬于 CNCF 基金會,被 AWS、Google、Microsoft、Alibaba 等大型互聯(lián)網(wǎng)公司廣泛的使用。
最初,在 2013 年 6 月份由 CoreOS 公司向 GitHub 中提交了第一個版本的初始代碼。
到了 2014 年的 6 月,社區(qū)發(fā)生了一件事情,Kubernetes v0.4 版本發(fā)布。這里有必要介紹一下 Kubernetes 項目,它首先是一個容器管理平臺,由谷歌開發(fā)并貢獻給社區(qū),因為它集齊了谷歌在容器調(diào)度以及集群管理等領(lǐng)域的多年經(jīng)驗,從誕生之初就備受矚目。在 Kubernetes v0.4 版本中,它使用了 etcd 0.2 版本作為實驗核心元數(shù)據(jù)的存儲服務(wù),自此 etcd 社區(qū)得到了飛速的發(fā)展。
很快,在 2015 年 2 月份,etcd 發(fā)布了第一個正式的穩(wěn)定版本 2.0。在 2.0 版本中,etcd 重新實踐了 Raft 一致性算法,另外用戶提供了一個簡單的樹形數(shù)據(jù)視圖,在 2.0 版本中 etcd 支持每秒超過 1000 次的寫入性能,滿足了當時絕大多數(shù)的應(yīng)用場景需求。2.0 版本發(fā)布之后,經(jīng)過不斷的迭代與改進,其原有的數(shù)據(jù)存儲方案逐漸成為了新世紀的性能瓶頸,因此 etcd 啟動了 v3 版本的方案設(shè)計。
2017 年 1 月份的時候,etcd 發(fā)布了 3.1 版本,基本上標志著 v3 版本方案的全面成熟。在 v3 版本中 etcd 提供了一套全新的 API,并且重新實踐了更有效的一致性讀取方法,同時提供了一個 gRPC 接口。gRPC 的 proxy 用于擴展 etcd 的讀取性能,同時在 v3 版本的方案中包含了大量的 GC 的優(yōu)化,極大地提高了 etcd 的性能。在該版本中 etcd 可以支持每秒超過 10000 次的寫入。
2018 年,CNCF 基金會下的眾多項目都使用了 etcd 作為其核心的數(shù)據(jù)存儲。據(jù)不完全統(tǒng)計,使用 etcd 的項目超過了 30 個,在同年 11 月份,etcd 項目自身也成為了 CNCF 旗下的孵化項目。進入 CNCF 基金會后,etcd 擁有了超過 400 個貢獻組,其中包含了來自 AWS、Google、Alibaba 等 8 個公司的 9 個項目維護者。
2019 年,etcd 即將發(fā)布全新的 3.4 版本,該版本由 Google、Alibaba 等公司聯(lián)合打造,將進一步改進 etcd 的性能及穩(wěn)定性,以滿足在超大型公司使用中苛刻的場景要求。
架構(gòu)及內(nèi)部機制解析
內(nèi)部機制解析
etcd 是一個分布式的、可靠的 key-value 存儲系統(tǒng),它用于存儲分布式系統(tǒng)中的關(guān)鍵數(shù)據(jù),這個定義非常重要。
一個 etcd 集群,通常會由 3 個或者 5 個節(jié)點組成,多個節(jié)點之間,通過一個叫做 Raft 一致性算法的方式完成分布式一致性協(xié)同,算法會選舉出一個主節(jié)點作為 leader,由 leader 負責數(shù)據(jù)的同步與數(shù)據(jù)的分發(fā),當 leader 出現(xiàn)故障后,系統(tǒng)會自動地選取另一個節(jié)點成為 leader,并重新完成數(shù)據(jù)的同步與分發(fā)??蛻舳嗽诒姸嗟?leader 中,僅需要選擇其中的一個就可以完成數(shù)據(jù)的讀寫。
在 etcd 整個的架構(gòu)中,有一個非常關(guān)鍵的概念叫做 quorum,quorum 的定義是 =(n+1)/2,也就是說超過集群中半數(shù)節(jié)點組成的一個團體,在 3 個節(jié)點的集群中,etcd 可以容許 1 個節(jié)點故障,也就是只要有任何 2 個節(jié)點重合,etcd 就可以繼續(xù)提供服務(wù)。同理,在 5 個節(jié)點的集群中,只要有任何 3 個節(jié)點重合,etcd 就可以繼續(xù)提供服務(wù)。這也是 etcd 集群高可用的關(guān)鍵。
當我們在允許部分節(jié)點故障之后,繼續(xù)提供服務(wù),這里就需要解決一個非常復(fù)雜的問題,即分布式一致性。在 etcd 中,該分布式一致性算法由 Raft 一致性算法完成,這個算法本身是比較復(fù)雜的,我們這里就不展開詳細介紹了。
但是這里面有一個關(guān)鍵點,它基于一個前提:任意兩個 quorum 的成員之間一定會有一個交集,也就是說只要有任意一個 quorum 存活,其中一定存在某一個節(jié)點,它包含著集群中最新的數(shù)據(jù)。正是基于這個假設(shè),這個一致性算法就可以在一個 quorum 之間采用這份最新的數(shù)據(jù)去完成數(shù)據(jù)的同步,從而保證整個集群向前衍進的過程中其數(shù)據(jù)保持一致。
雖然 etcd 內(nèi)部的機制比較復(fù)雜,但是 etcd 給客戶提供的接口是比較簡單的。如上圖所示,我們可以通過 etcd 提供的客戶端去訪問集群的數(shù)據(jù),也可以直接通過 http 的方式,類似像 curl 命令直接訪問 etcd。在 etcd 內(nèi)部,其數(shù)據(jù)表示也是比較簡單的,我們可以直接把 etcd 的數(shù)據(jù)存儲理解為一個有序的 map,它存儲著 key-value 數(shù)據(jù)。同時 etcd 為了方便客戶端去訂閱資料的數(shù)據(jù),也支持了一個 watch 機制,我們可以通過 watch 實時地拿到 etcd 中數(shù)據(jù)的增量更新,從而保持與 etcd 中的數(shù)據(jù)同步。
etcd API 接口
接下來我們看一下 etcd 提供的接口,這里將 etcd 的接口分為了 5 組:
- 第一組是 Put 與 Delete。上圖可以看到 put 與 delete 的操作都非常簡單,只需要提供一個 key 和一個 value,就可以向集群中寫入數(shù)據(jù)了,那么在刪除數(shù)據(jù)的時候,只需要提供 key 就可以了;
- 第二組操作是查詢操作。查詢操作 etcd 支持兩種類型的查詢:第一種是指定單個 key 的查詢,第二種是指定的一個 key 的范圍;
- 第三組操作:etcd 啟動了 Watch 的機制,也就是我們前面提到的用于實現(xiàn)增量的數(shù)據(jù)更新,watch 也是有兩種使用方法,第一種是指定單個 key 的 Watch,另一種是指定一個 key 的前綴。在實際應(yīng)用場景的使用過程中,經(jīng)常采用第二種;
- 第四組:API 是 etcd 提供的一個事務(wù)操作,可以通過指定某些條件,當條件成立的時候執(zhí)行一組操作。當條件不成立的時候執(zhí)行另外一組操作;
- 第五組是 Leases 接口。Leases 接口是分布式系統(tǒng)中常用的一種設(shè)計模式,后面會具體介紹。
etcd 的數(shù)據(jù)版本號機制
要正確使用 etcd 的 API,必須要知道內(nèi)部對應(yīng)數(shù)據(jù)版本號的基本原理。
首先 etcd 中有個 term 的概念,代表的是整個集群 Leader 的標志。當集群發(fā)生 Leader 切換,比如說 Leader 節(jié)點故障,或者說 Leader 節(jié)點網(wǎng)絡(luò)出現(xiàn)問題,再或者是將整個集群停止后再次拉起,這個時候都會發(fā)生 Leader 的切換。當 Leader 切換的時候,term 的值就會 +1。
第二個版本號叫做 revision,revision 代表的是全局數(shù)據(jù)的版本。當數(shù)據(jù)發(fā)生變更,包括創(chuàng)建、修改、刪除,revision 對應(yīng)的都會 +1。在任期內(nèi),revision 都可以保持全局單調(diào)遞增的更改。正是 revision 的存在才使得 etcd 既可以支持數(shù)據(jù)的 MVCC,也可以支持數(shù)據(jù)的 Watch。
對于每一個 KeyValue 數(shù)據(jù),etcd 中都記錄了三個版本:
第一個版本叫做 create_revision,是 KeyValue 在創(chuàng)建的時候生成的版本號;
第二個叫做 mod_revision,是其數(shù)據(jù)被操作的時候?qū)?yīng)的版本號;
第三個 version 就是一個計數(shù)器,代表了 KeyValue 被修改了多少次。
這里可以用圖的方式給大家展示一下:
在同一個 Leader 任期之內(nèi),我們發(fā)現(xiàn)所有的修改操作,其對應(yīng)的 term 值都等于 2,始終保持不變,而 revision 則保持單調(diào)遞增。
當重啟集群之后,我們會發(fā)現(xiàn)所有的修改操作對應(yīng)的 term 值都變成了 3。在新的任期內(nèi),所有的 term 值都等于3,且不會發(fā)生變化。而對應(yīng)的 revision 值同樣保持單調(diào)遞增。
從一個更大的維度去看,可以發(fā)現(xiàn):在多個任期內(nèi),其數(shù)據(jù)對應(yīng)的 revision 值會保持全局的單調(diào)遞增。
etcd mvcc & streaming watch
了解 etcd 的版本號控制后,接下來介紹一下 etcd 多版本號的并發(fā)控制以及 watch 的使用方法。
首先,在 etcd 中,支持對同一個 Key 發(fā)起多次數(shù)據(jù)修改。因為已經(jīng)知道每次數(shù)據(jù)修改都對應(yīng)一個版本號,多次修改就意味著一個 key 中存在多個版本,在查詢數(shù)據(jù)的時候可以通過不指定版本號查詢,這時 etcd 會返回該數(shù)據(jù)的最新版本。當我們指定一個版本號查詢數(shù)據(jù)后,可以獲取到一個 Key 的歷史版本。
為每次操作修改數(shù)據(jù)的時候都會對應(yīng)一個版本號。 在 watch 的時候指定數(shù)據(jù)的版本,創(chuàng)建一個 watcher,并通過這個 watcher 提供的一個數(shù)據(jù)管道,能夠獲取到指定的 revision 之后所有的數(shù)據(jù)變更。如果指定的 revision 是一個舊版本,可以立即拿到從舊版本到當前版本所有的數(shù)據(jù)更新。并且,watch 的機制會保證 etcd 中,該 Key 的數(shù)據(jù)發(fā)生后續(xù)的修改后,依然可以從這個數(shù)據(jù)管道中拿到數(shù)據(jù)增量的更新。
為了更好地理解 etcd 的多版本控制以及 watch 的機制,可以簡單的介紹一下 etcd 的內(nèi)部實現(xiàn)。
在 etcd 中所有的數(shù)據(jù)都存儲在一個 b+tree 中。b+tree 是保存在磁盤中,并通過 mmap 的方式映射到內(nèi)存用來查詢操作。
在 b+tree 中(如圖所示灰色部分),維護著 revision 到 value 的映射關(guān)系。也就是說當指定 revision 查詢數(shù)據(jù)的時候,就可以通過該 b+tree 直接返回數(shù)據(jù)。當我們通過 watch 來訂閱數(shù)據(jù)的時候,也可以通過這個 b+tree 維護的 revision 到 value 映射關(guān)系,從而通過指定的 revision 開始遍歷這個 b+tree,拿到所有的數(shù)據(jù)更新。
同時在 etcd 內(nèi)部還維護著另外一個 b+tree。它管理著 key 到 revision 的映射關(guān)系。當需要查詢 Key 對應(yīng)數(shù)據(jù)的時候,會通過藍色方框的 b+tree,將 key 翻譯成 revision。再通過灰色框 b+tree 中的 revision 獲取到對應(yīng)的 value。至此就能滿足客戶端不同的查詢場景了。
這里需要提兩點:
- 一個數(shù)據(jù)是有多個版本的;
- 在 etcd 持續(xù)運行過程中會不斷的發(fā)生修改,意味著 etcd 中內(nèi)存及磁盤的數(shù)據(jù)都會持續(xù)增長。這對資源有限的場景來說是無法接受的。因此在 etcd 中會周期性的運行一個 Compaction 的機制來清理歷史數(shù)據(jù)。對于一個 Key 的歷史版本數(shù)據(jù),可以選擇清理掉。
etcd mini-transactions
在理解了 mvcc 機制及 watch 機制之后,來介紹一下 etcd 提供的 mini-transactions 機制。etcd 的 transaction 機制比較簡單,基本可以理解為一段 if-else 程序,在 if 中可以提供多個操作。
在上圖的示例中,if 里面寫了兩個條件。當 Value(key1) 大于“bar”,并且 Version(key1) 的版本等于 2 的時候,執(zhí)行 Then 里面指定的操作:修改 Key2 的數(shù)據(jù)為 valueX,同時刪除 Key3 的數(shù)據(jù)。如果不滿足條件,則執(zhí)行另外一個操作:Key2 修改為 valueY。
在 etcd 內(nèi)部會保證整個事務(wù)操作的原子性。也就是說 If 操作所有的比較條件,其看到的視圖,一定是一致的。同時它能夠確保在爭執(zhí)條件中,多個操作的原子性不會出現(xiàn) etc 僅執(zhí)行了一半的情況。
通過 etcd 提供的事務(wù)操作,我們可以在多個競爭中去保證數(shù)據(jù)讀寫的一致性,比如說前面已經(jīng)提到過的 Kubernetes 項目,它正是利用了 etcd 的事務(wù)機制,來實現(xiàn)多個 KubernetesAPI server 對同樣一個數(shù)據(jù)修改的一致性。
etcd lease 的概念及用法
lease 是分布式系統(tǒng)中一個常見的概念,用于代表一個租約。通常情況下,在分布式系統(tǒng)中需要去檢測一個節(jié)點是否存活的時候,就需要租約機制。
上圖示例中首先創(chuàng)建了一個 10s 的租約,如果創(chuàng)建租約后不做任何的操作,那么 10s 之后,這個租約就會自動過期。
在這里,接著將 key1 和 key2 兩個 key value 綁定到這個租約之上,這樣當租約過期時,etcd 就會自動清理掉 key1 和 key2 對應(yīng)的數(shù)據(jù)。
如果希望這個租約永不過期,比如說需要檢測分布式系統(tǒng)中一個進程是否存活,那么就會在這個分布式進程中去訪問 etcd 并且創(chuàng)建一個租約,同時在該進程中去調(diào)用 KeepAlive 的方法,與 etcd 保持一個租約不斷的續(xù)約。試想一下,如果這個進程掛掉了,這時就沒有辦法再繼續(xù)去開發(fā) Alive。租約在進程掛掉的一段時間就會被 etcd 自動清理掉。所以可以通過這個機制來判定節(jié)點是否存活。
在 etcd 中,允許將多個 key 關(guān)聯(lián)在統(tǒng)一的 lease 之上,這個設(shè)計是非常巧妙的,事實上最初的設(shè)計也不是這個樣子。通過這種方法,將多個 key 綁定在同一個lease對象,可以大幅地減少 lease 對象刷新的時間。試想一下,如果大量的 key 都需要綁定在同一個 lease 對象之上,每一個 key 都去更新這個租約的話,這個 etcd 會產(chǎn)生非常大的壓力。通過支持加多個 key 綁定在同一個 lease 之上,它既不失靈活性同時能夠大幅改進 etcd 整個系統(tǒng)的性能。
實例演示
etcd 基本的讀寫操作
這里為大家以實際 demo 的方式來演示一些 etcd 的基本的操作。首先啟動一個 etcd,會發(fā)現(xiàn) etcd 已經(jīng)成功啟動了。
啟動 etcd 之后,就可以通過 etcd 客戶端來操作 etcd 的數(shù)據(jù)了。比如說向 etcd 中插入一條數(shù)據(jù),在插入一條數(shù)據(jù)后,可以通過 get 方法來查詢到這條數(shù)據(jù)。如下圖所示:
為了展示 etcd 查詢的基本內(nèi)容,這里直接創(chuàng)建 10 個 key value,分別為 key0~key9,所以我們就向 etcd 中插入了 10 條數(shù)據(jù)。
可以查詢其中的 key5,同時也支持查詢 key 的范圍。指定一個 key 的起始的范圍,以及 key 的結(jié)束范圍。注意:結(jié)束范圍是不包含的,也就是說當查詢 key2~key6 的時候,etcd 會返回 key2、key3、key4、key5 四條數(shù)據(jù)。
etcd 還支持指定前綴的查詢。通過這種方式,可以發(fā)現(xiàn) etcd 會返回 key0~key9 所有的數(shù)據(jù)。
前面提到的 etcd 中的版本,對于 key0 來說,可以通過 -w 指定 json 的方式來輸出。下面格式化一個 json 字符串,發(fā)現(xiàn)該操作對應(yīng)返回了其中的第一條數(shù)據(jù),并且在返回體中包含了一個消息頭。該消息頭描述了 etcd 全局的信息,其中的 class ID 和 member ID 暫時先忽略掉。那么這里的 raft_term 就是我們在前面提到的 etcd 的全局版本號 term,當前 term 值是 2,發(fā)現(xiàn)所有的操作中 term 值對應(yīng)的都是 2。
假如換成 key5,如下圖所示,發(fā)現(xiàn)對應(yīng)的 term 值依然是 2。同時集群全局的版本號 revision 當前是 12,因為我們沒有發(fā)起任何的數(shù)據(jù)修改。
這時可以嘗試再插入一條數(shù)據(jù)。當再次查詢 etcd key5 的數(shù)據(jù)時,會發(fā)現(xiàn)全局的 revision 已經(jīng)變成了 13,因為剛才又新增了一條數(shù)據(jù),同時當前的 term 值依然是保持不變的。
在返回的數(shù)據(jù)中,所有包含的 key value 以外,它還包含著 etcd 提供的三個 revision。這里我們會發(fā)現(xiàn),key5 對應(yīng)創(chuàng)建的 revision 是 8,同時它修改的 revision 也是 8,因為我們沒有對 key5 發(fā)起過任何的修改,與此同時,它對應(yīng)的 version 是 1。
當對 key5 發(fā)起修改的時候,下圖中可以看到,其對應(yīng)的 mod_revision 已經(jīng)變成了 14,修改的 revision 并不是 +1 的關(guān)系,而是采用當前操作對應(yīng)的 revision。同樣,因為已經(jīng)發(fā)生了一次修改,那么 key5 的版本號就會從 1 變成 2。
那么如果再一次修改 key5 的數(shù)據(jù),保持數(shù)據(jù)不變,還是 x,會發(fā)生什么情況呢?
如下圖所示,集群全局的 revision 變成了 15,key5 的 mod_revision 也變成了 15,key5 的 version 變成了 3??梢园l(fā)現(xiàn):對同一個 key 寫入同一個 value,其版本號依然會變化。
如果刪除這個數(shù)據(jù),重新產(chǎn)生 key5 這個數(shù)據(jù),從下圖可以發(fā)現(xiàn),當前的 revision 又增加了一次。因為刪除也是一次修改操作,但數(shù)據(jù)已經(jīng)沒有了。
如果重新插入 key5,這時全局的 revision 變成了 17、key5 的 create_revision 是 17、對應(yīng)修改的 revision 也是 17;但 value 值卻變成了 1,又重新開始計數(shù),這是因為 value 代表著 key 被修改的次數(shù)。
在同一個任期內(nèi)的 term 值是不發(fā)生變化的。如果嘗試停掉 etcd,并且重新啟動 etcd。當再次查詢 key5 的數(shù)據(jù)時,將會發(fā)現(xiàn)集群已經(jīng)從 2 變成了 3,但全局的數(shù)據(jù)的 revision 是不變的。
如果這時再次修改 key5,會發(fā)現(xiàn)跟剛才的規(guī)律一樣:集群全局的 revision +1、key5 創(chuàng)建的 revision 保持不變。但是修改的 revision 為本次操作的版本,同時,其 value 修改過一次之后 version 變成了2。
這里需要注意到:key 和 value 在 etcd 中的表示并不是字符串的形式,而是二進制的形式,這里輸出的時候是以 base64 編碼的。
如果需要知道 etcd 的數(shù)據(jù)內(nèi)容,那么可以通過 get key5,或者可以通過反解 base 64 的方式來實現(xiàn)。如下圖執(zhí)行命令后得到的值為 valueX。
etcd 的 watch操作
接下來看一下 etcd 提供的 watch 操作,來 watch key5 的變化。再開一個窗口去寫入 key5 的數(shù)據(jù),當數(shù)據(jù)被寫入的時候,原本的 watch 就能及時拿到數(shù)據(jù)的更新,它可以非常實時地感覺到數(shù)據(jù)的變化。同時嘗試寫入 key4 的數(shù)據(jù),則 watch 是不會收到數(shù)據(jù)更新的。
前面我們提到了 etcd 是支持指定前綴 watch的。如果指定前綴 key,那么在寫入 key4 的時候,我們能收到更新;寫入 key5 的時候,依然能收到更新。通過這種方式,我們就可以實現(xiàn) etcd 的數(shù)據(jù)同步了。
這里可以給大家留一個課后作業(yè),可以嘗試使用 etcd 的客戶端,來實現(xiàn)一個同步 etcd 指定前綴的數(shù)據(jù)版本的功能。
etcd 事務(wù)操作
etcd 客戶端還提供了一些事務(wù)操作的語義。比如說 key5 的數(shù)據(jù)等于一個不成立的條件,隨便寫一個“whatever”,那么顯然成功操作的時候?qū)?#xff1b;成功操作寫一個讀取操作,失敗的時候嘗試將 key5 重新寫為 value5,同時將 key4 寫為 valueX。當事務(wù)成功以后,重新去查 key5 的數(shù)據(jù),就會發(fā)現(xiàn) key5 已經(jīng)被寫成 value5 了,key4 也被更新為對應(yīng)的 valueX。
demo 的演示到這里就結(jié)束了。
三、典型的使用場景介紹
元數(shù)據(jù)存儲-Kubernetes
因為 Kubernetes 將自身所用的狀態(tài)設(shè)備存儲在 etcd 中,其狀態(tài)數(shù)據(jù)的存儲的復(fù)雜性將 etcd 給 cover 掉之后,Kubernetes 系統(tǒng)自身不需要再樹立復(fù)雜的狀態(tài)流轉(zhuǎn),因此自身的系統(tǒng)設(shè)立架構(gòu)也得到了大幅的簡化。
Server Discovery (Naming Service)
第二個場景是 Service Discovery,也叫做名字服務(wù)。在分布式系統(tǒng)中,通常會出現(xiàn)的一個模式就是需要后端多個,可能是成百上千個進程來提供一組對等的服務(wù),比如說檢索服務(wù)、推薦服務(wù)。
對于這樣一種后端服務(wù),通常情況下為了簡化后端服務(wù)的運維成本,后端的這一進程會被類似 Kubernetes 這樣的集群管理系統(tǒng)所調(diào)度,也就是說,我們事先無法知道這一組進程分布在哪里。為了解決這個問題,可以利用 etc來解決資源注冊的問題,當這一組后端進程被調(diào)度,在進程內(nèi)部啟動之后,可以將自身所在的地址注冊到 etcd。
從而使得 API 網(wǎng)關(guān)能夠通過 etcd 及時感知到后端進程的地址,這樣當后端進程發(fā)生故障遷移的時候,會重新注冊到 etcd 中,使得 API 網(wǎng)關(guān)能夠及時地感知到新的集群地址。同時,因為 etcd 提供的 Lease 操作,可以及時感知到進程狀態(tài)的變化,如果進程運行過程中死掉了,那么網(wǎng)關(guān)可以及時感知到進程狀態(tài)的變化,從而將流量自動地切到其他的進程。
通過這種方式,整個狀態(tài)數(shù)據(jù)被 etcd 接管,那么 API 網(wǎng)關(guān)本身也是無狀態(tài)的,它可以水平地擴展來服務(wù)更多的客戶。同時得益于 etcd 的良好性能,可以支持上萬個后端進程的節(jié)點,基本上這種架構(gòu)可以服務(wù)于非常大型的企業(yè)。
Distributed Coordination: leader election
第三個場景是分布式場景中比較常見的一個選主的場景。
我們知道在分布式系統(tǒng)中,有一種典型的設(shè)計模式就是 Master+Slave。通常情況下,Slave 提供了 CPU 內(nèi)存磁盤以及網(wǎng)絡(luò)的各種資源 ,而 Master 用來控制這些資源的協(xié)同,Master 內(nèi)部會存儲一些狀態(tài)數(shù)據(jù),以及實現(xiàn)一組和業(yè)務(wù)邏輯相關(guān)的控制器,而 Slave 之間會和 Master 保持一個心跳的交互。
典型的分布式存儲服務(wù)以及分布式計算服務(wù),比如說 Hadoop,HDFS 等等,它們都是采用了類似這樣的設(shè)計模式。這樣的設(shè)計模式會有一個典型的問題:Master 節(jié)點的可用性。當 Master 故障以后,整個集群的服務(wù)就被停掉了,沒有辦法再服務(wù)用戶的請求。
為了解決這個問題,需要去啟動多個 Master。那么多個 Master 就會面臨一個問題:誰來提供這個服務(wù)?因為通常情況下分布式系統(tǒng)和 Master 都是有狀態(tài)邏輯的,無法允許多個 Master 同時運行。
可以通過 etcd 來實現(xiàn)選主,將其中的一個 Master 選主成 Leader,負責控制整個集群中所有 Slave 的狀態(tài)。被選主的 Leader 可以將自己的 IP 注冊到 etcd 中,使得 Slave 節(jié)點能夠及時獲取到當前組件的地址,從而使得系統(tǒng)按照之前單個 Master 節(jié)點的方式繼續(xù)工作。當 Leader 節(jié)點發(fā)生異常之后,通過 etcd 能夠選取出一個新的節(jié)點成為主節(jié)點,并且注冊新的 IP 之后,Slave 又能夠拉取新的主節(jié)點的 IP,從而會繼續(xù)恢復(fù)服務(wù)。這一架構(gòu),在分布式系統(tǒng)中,被廣泛使用。
Distributed Coordination 分布式系統(tǒng)并發(fā)控制
最后來看分布式協(xié)同中的另外一個問題。在分布式系統(tǒng)中,當我們?nèi)?zhí)行一些任務(wù),比如說去升級 OS、或者說升級 OS 上的軟件的時候、又或者去執(zhí)行一些計算任務(wù)的時候,通常情況下需要控制任務(wù)的并發(fā)度。因為任務(wù)到了后端服務(wù),通常是有容量瓶頸的。無法在同一個時刻,同時拉起成千上萬的任務(wù)。這樣后端的存儲系統(tǒng)或者是網(wǎng)絡(luò)資源都是吃不消的。
因此需要控制任務(wù)執(zhí)行的并發(fā)度。在這個設(shè)計模式的進程中通過 etcd 提供的一些基本 API 操作來完成分布式的協(xié)同。這樣當?shù)谝粋€進程執(zhí)行完畢以后,第二個進程就可以開始執(zhí)行。同時利用 etcd 的進程存活性檢測機制可以做到:當將任務(wù)分發(fā)給一個進程,但這個進程沒有執(zhí)行完就已經(jīng)死掉之后,可以繼續(xù)換下一個進程繼續(xù)工作。
也就是說,在這個模式中通過 etcd 去實現(xiàn)一個分布式的信號量,并且它支持能夠自動地剔除掉故障節(jié)點。在進程執(zhí)行過程中,如果進程的運行周期比較長,我們可以將進程運行過程中的一些狀態(tài)數(shù)據(jù)存儲到 etcd,從而使得當進程故障之后且需要恢復(fù)到其他地方時,能夠從 etcd 中去恢復(fù)一些執(zhí)行狀態(tài),而不需要重新去完成整個的計算邏輯,以此來加速整個任務(wù)的執(zhí)行效率。
本節(jié)總結(jié)
本節(jié)課的主要內(nèi)容就到此為止了,這里為大家簡單總結(jié)一下:
- 第一部分,為大家介紹了 etcd 項目是如何誕生的,以及在 etcd 發(fā)展的過程中它經(jīng)歷的幾個階段;
- 第二部分,為大家介紹了 etcd 的架構(gòu)以及其內(nèi)部的基本操作接口,在理解 etcd 是如何實現(xiàn)高可用的基礎(chǔ)之上,結(jié)合了實踐的方式展示了 etcd 數(shù)據(jù)的一些基本操作以及其內(nèi)部的版本管理機制;
- 第三部分,介紹了三種典型的 etcd 使用場景,以及在對應(yīng)的場景下,分布式系統(tǒng)的設(shè)計思路。
那么下一節(jié)課程,將會繼續(xù)為大家?guī)?etcd 的內(nèi)容,它包含了阿里云在 etcd 生產(chǎn)上面的實踐經(jīng)驗,包括如何去構(gòu)建一個穩(wěn)定高性能可擴展的 etcd 存儲服務(wù)。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的课时 16 深入理解 etcd:基于原理解析(曾凡松)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。