網上關於 Etcd 的使用介紹的文章很多,但分析具體架構實現的文章很少,同時 Etcd v3的文檔也很是稀缺。本文經過分析 Etcd 的架構與實現,瞭解其優缺點以及瓶頸點,一方面能夠學習分佈式系統的架構,另一方面也能夠保證在業務中正確使用 Etcd,知其然同時知其因此然,避免誤用。最後介紹 Etcd 周邊的工具和一些使用注意事項。java
閱讀對象:分佈式系統愛好者,正在或者打算在項目中使用Etcd的開發人員。node
Etcd 按照官方介紹android
Etcd is a distributed, consistent key-value store for shared configuration and service discoverygit
是一個分佈式的,一致的 key-value 存儲,主要用途是共享配置和服務發現。Etcd 已經在不少分佈式系統中獲得普遍的使用,本文的架構與實現部分主要解答如下問題:github
Etcd是如何實現一致性的? Etcd的存儲是如何實現的? Etcd的watch機制是如何實現的? Etcd的key過時機制是如何實現的? 1、爲何須要 Etcd ?golang
全部的分佈式系統,都面臨的一個問題是多個節點之間的數據共享問題,這個和團隊協做的道理是同樣的,成員能夠分頭幹活,但老是須要共享一些必須的信息,好比誰是 leader, 都有哪些成員,依賴任務之間的順序協調等。因此分佈式系統要麼本身實現一個可靠的共享存儲來同步信息(好比 Elasticsearch ),要麼依賴一個可靠的共享存儲服務,而 Etcd 就是這樣一個服務。算法
2、Etcd 提供什麼能力?docker
Etcd 主要提供如下能力,已經熟悉 Etcd 的讀者能夠略過本段。數據庫
提供存儲以及獲取數據的接口,它經過協議保證 Etcd 集羣中的多個節點數據的強一致性。用於存儲元信息以及共享配置。 提供監聽機制,客戶端能夠監聽某個key或者某些key的變動(v2和v3的機制不一樣,參看後面文章)。用於監聽和推送變動。 提供key的過時以及續約機制,客戶端經過定時刷新來實現續約(v2和v3的實現機制也不同)。用於集羣監控以及服務註冊發現。 提供原子的CAS(Compare-and-Swap)和 CAD(Compare-and-Delete)支持(v2經過接口參數實現,v3經過批量事務實現)。用於分佈式鎖以及leader選舉。 更詳細的使用場景不在這裏描述,有興趣的能夠參看文末infoq的一篇文章。apache
3、Etcd 如何實現一致性的?
說到這個就不得不提及raft協議。但這篇文章不是專門分析raft的,篇幅所限,不能詳細分析,有興趣的建議看文末原始論文地址以及raft協議的一個動畫。便於看後面的文章,我這裏簡單作個總結:
raft經過對不一樣的場景(選主,日誌複製)設計不一樣的機制,雖然下降了通用性(相對paxos),但同時也下降了複雜度,便於理解和實現。 raft內置的選主協議是給本身用的,用於選出主節點,理解raft的選主機制的關鍵在於理解raft的時鐘週期以及超時機制。 理解 Etcd 的數據同步的關鍵在於理解raft的日誌同步機制。 Etcd 實現raft的時候,充分利用了go語言CSP併發模型和chan的魔法,想更進行一步瞭解的能夠去看源碼,這裏只簡單分析下它的wal日誌。
wal日誌是二進制的,解析出來後是以上數據結構LogEntry。其中第一個字段type,只有兩種,一種是0表示Normal,1表示ConfChange(ConfChange表示 Etcd 自己的配置變動同步,好比有新的節點加入等)。第二個字段是term,每一個term表明一個主節點的任期,每次主節點變動term就會變化。第三個字段是index,這個序號是嚴格有序遞增的,表明變動序號。第四個字段是二進制的data,將raft request對象的pb結構整個保存下。Etcd 源碼下有個tools/etcd-dump-logs,能夠將wal日誌dump成文本查看,能夠協助分析raft協議。
raft協議自己不關心應用數據,也就是data中的部分,一致性都經過同步wal日誌來實現,每一個節點將從主節點收到的data apply到本地的存儲,raft只關心日誌的同步狀態,若是本地存儲實現的有bug,好比沒有正確的將data apply到本地,也可能會致使數據不一致。
4、Etcd v2 與 v3
Etcd v2 和 v3 本質上是共享同一套 raft 協議代碼的兩個獨立的應用,接口不同,存儲不同,數據互相隔離。也就是說若是從 Etcd v2 升級到 Etcd v3,原來v2 的數據仍是隻能用 v2 的接口訪問,v3 的接口建立的數據也只能訪問經過 v3 的接口訪問。因此咱們按照 v2 和 v3 分別分析。
5、Etcd v2 Watch,以及過時機制
Etcd v2 是個純內存的實現,並未實時將數據寫入到磁盤,持久化機制很簡單,就是將store整合序列化成json寫入文件。數據在內存中是一個簡單的樹結構。好比如下數據存儲到 Etcd 中的結構就如圖所示。
/nodes/1/name node1 /nodes/1/ip 192.168.1.1 store中有一個全局的currentIndex,每次變動,index會加1.而後每一個event都會關聯到currentIndex.
當客戶端調用watch接口(參數中增長 wait參數)時,若是請求參數中有waitIndex,而且waitIndex 小於 currentIndex,則從 EventHistroy 表中查詢index小於等於waitIndex,而且和watch key 匹配的 event,若是有數據,則直接返回。若是歷史表中沒有或者請求沒有帶 waitIndex,則放入WatchHub中,每一個key會關聯一個watcher列表。 當有變動操做時,變動生成的event會放入EventHistroy表中,同時通知和該key相關的watcher。
這裏有幾個影響使用的細節問題:
EventHistroy 是有長度限制的,最長1000。也就是說,若是你的客戶端停了許久,而後從新watch的時候,可能和該waitIndex相關的event已經被淘汰了,這種狀況下會丟失變動。 若是通知watch的時候,出現了阻塞(每一個watch的channel有100個緩衝空間),Etcd 會直接把watcher刪除,也就是會致使wait請求的鏈接中斷,客戶端須要從新鏈接。 Etcd store的每一個node中都保存了過時時間,經過定時機制進行清理。 從而能夠看出,Etcd v2 的一些限制:
過時時間只能設置到每一個key上,若是多個key要保證生命週期一致則比較困難。 watch只能watch某一個key以及其子節點(經過參數 recursive),不能進行多個watch。 很難經過watch機制來實現完整的數據同步(有丟失變動的風險),因此當前的大多數使用方式是經過watch得知變動,而後經過get從新獲取數據,並不徹底依賴於watch的變動event。 6、Etcd v3 存儲,Watch,以及過時機制
Etcd v3 將watch和store拆開實現,咱們先分析下store的實現。
Etcd v3 store 分爲兩部分,一部分是內存中的索引,kvindex,是基於google開源的一個golang的btree實現的,另一部分是後端存儲。按照它的設計,backend能夠對接多種存儲,當前使用的boltdb。boltdb是一個單機的支持事務的kv存儲,Etcd 的事務是基於boltdb的事務實現的。Etcd 在boltdb中存儲的key是reversion,value是 Etcd 本身的key-value組合,也就是說 Etcd 會在boltdb中把每一個版本都保存下,從而實現了多版本機制。
舉個例子: 用etcdctl經過批量接口寫入兩條記錄:
etcdctl txn <<<' put key1 "v1" put key2 "v2" ' 再經過批量接口更新這兩條記錄:
etcdctl txn <<<' put key1 "v12" put key2 "v22" ' boltdb中其實有了4條數據:
rev={3 0}, key=key1, value="v1" rev={3 1}, key=key2, value="v2" rev={4 0}, key=key1, value="v12" rev={4 1}, key=key2, value="v22" reversion主要由兩部分組成,第一部分main rev,每次事務進行加一,第二部分sub rev,同一個事務中的每次操做加一。如上示例,第一次操做的main rev是3,第二次是4。固然這種機制你們想到的第一個問題就是空間問題,因此 Etcd 提供了命令和設置選項來控制compact,同時支持put操做的參數來精確控制某個key的歷史版本數。
瞭解了 Etcd 的磁盤存儲,能夠看出若是要從boltdb中查詢數據,必須經過reversion,但客戶端都是經過key來查詢value,因此 Etcd 的內存kvindex保存的就是key和reversion以前的映射關係,用來加速查詢。
而後咱們再分析下watch機制的實現。Etcd v3 的watch機制支持watch某個固定的key,也支持watch一個範圍(能夠用於模擬目錄的結構的watch),因此 watchGroup 包含兩種watcher,一種是 key watchers,數據結構是每一個key對應一組watcher,另一種是 range watchers, 數據結構是一個 IntervalTree(不熟悉的參看文文末連接),方便經過區間查找到對應的watcher。
同時,每一個 WatchableStore 包含兩種 watcherGroup,一種是synced,一種是unsynced,前者表示該group的watcher數據都已經同步完畢,在等待新的變動,後者表示該group的watcher數據同步落後於當前最新變動,還在追趕。
當 Etcd 收到客戶端的watch請求,若是請求攜帶了revision參數,則比較請求的revision和store當前的revision,若是大於當前revision,則放入synced組中,不然放入unsynced組。同時 Etcd 會啓動一個後臺的goroutine持續同步unsynced的watcher,而後將其遷移到synced組。也就是這種機制下,Etcd v3 支持從任意版本開始watch,沒有v2的1000條歷史event表限制的問題(固然這是指沒有compact的狀況下)。
另外咱們前面提到的,Etcd v2在通知客戶端時,若是網絡很差或者客戶端讀取比較慢,發生了阻塞,則會直接關閉當前鏈接,客戶端須要從新發起請求。Etcd v3爲了解決這個問題,專門維護了一個推送時阻塞的watcher隊列,在另外的goroutine裏進行重試。
Etcd v3 對過時機制也作了改進,過時時間設置在lease上,而後key和lease關聯。這樣能夠實現多個key關聯同一個lease id,方便設置統一的過時時間,以及實現批量續約。
相比Etcd v2, Etcd v3的一些主要變化:
接口經過grpc提供rpc接口,放棄了v2的http接口。優點是長鏈接效率提高明顯,缺點是使用不如之前方便,尤爲對不方便維護長鏈接的場景。 廢棄了原來的目錄結構,變成了純粹的kv,用戶能夠經過前綴匹配模式模擬目錄。 內存中再也不保存value,一樣的內存能夠支持存儲更多的key。 watch機制更穩定,基本上能夠經過watch機制實現數據的徹底同步。 提供了批量操做以及事務機制,用戶能夠經過批量事務請求來實現Etcd v2的CAS機制(批量事務支持if條件判斷)。 7、Etcd,Zookeeper,Consul 比較
這三個產品是常常被人拿來作選型比較的。 Etcd 和 Zookeeper 提供的能力很是類似,都是通用的一致性元信息存儲,都提供watch機制用於變動通知和分發,也都被分佈式系統用來做爲共享信息存儲,在軟件生態中所處的位置也幾乎是同樣的,能夠互相替代的。兩者除了實現細節,語言,一致性協議上的區別,最大的區別在周邊生態圈。Zookeeper 是apache下的,用java寫的,提供rpc接口,最先從hadoop項目中孵化出來,在分佈式系統中獲得普遍使用(hadoop, solr, kafka, mesos 等)。Etcd 是coreos公司旗下的開源產品,比較新,以其簡單好用的rest接口以及活躍的社區俘獲了一批用戶,在新的一些集羣中獲得使用(好比kubernetes)。雖然v3爲了性能也改爲二進制rpc接口了,但其易用性上比 Zookeeper 仍是好一些。 而 Consul 的目標則更爲具體一些,Etcd 和 Zookeeper 提供的是分佈式一致性存儲能力,具體的業務場景須要用戶本身實現,好比服務發現,好比配置變動。而Consul 則以服務發現和配置變動爲主要目標,同時附帶了kv存儲。 在軟件生態中,越抽象的組件適用範圍越廣,但同時對具體業務場景需求的知足上確定有不足之處。
8、Etcd 的周邊工具
在分佈式系統中,理想狀況下是應用程序直接和 Etcd 這樣的服務發現/配置中心交互,經過監聽 Etcd 進行服務發現以及配置變動。但咱們還有許多歷史遺留的程序,服務發現以及配置大多都是經過變動配置文件進行的。Etcd 本身的定位是通用的kv存儲,因此並無像 Consul 那樣提供實現配置變動的機制和工具,而 Confd 就是用來實現這個目標的工具。
Confd 經過watch機制監聽 Etcd 的變動,而後將數據同步到本身的一個本地存儲。用戶能夠經過配置定義本身關注那些key的變動,同時提供一個配置文件模板。Confd 一旦發現數據變動就使用最新數據渲染模板生成配置文件,若是新舊配置文件有變化,則進行替換,同時觸發用戶提供的reload腳本,讓應用程序從新加載配置。
Confd 至關於實現了部分 Consul 的agent以及consul-template的功能,做者是kubernetes的Kelsey Hightower,但大神貌似很忙,沒太多時間關注這個項目了,好久沒有發佈版本,咱們着急用,因此fork了一份本身更新維護,主要增長了一些新的模板函數以及對metad後端的支持。
服務註冊的實現模式通常分爲兩種,一種是調度系統代爲註冊,一種是應用程序本身註冊。調度系統代爲註冊的狀況下,應用程序啓動後須要有一種機制讓應用程序知道『我是誰』,而後發現本身所在的集羣以及本身的配置。Metad 提供這樣一種機制,客戶端請求 Metad 的一個固定的接口 /self,由 Metad 告知應用程序其所屬的元信息,簡化了客戶端的服務發現和配置變動邏輯。
Metad 經過保存一個ip到元信息路徑的映射關係來作到這一點,當先後端支持Etcd v3,提供簡單好用的 http rest 接口。 它會把 Etcd 的數據經過watch機制同步到本地內存中,至關於 Etcd 的一個代理。因此也能夠把它當作Etcd 的代理來使用,適用於不方便使用 Etcd v3的rpc接口或者想下降 Etcd 壓力的場景。
Etcd 官方那個一鍵搭建腳本有bug,我本身整理了一個腳本,經過docker的network功能,一鍵搭建一個本地的 Etcd 集羣便於測試和試驗。一鍵搭建腳本
9、Etcd 使用注意事項
若是集羣第一次初始化啓動的時候,有一臺節點未啓動,經過v3的接口訪問的時候,會報告Error: Etcdserver: not capable 錯誤。這是爲兼容性考慮,集羣啓動時默認的API版本是2.3,只有當集羣中的全部節點都加入了,確認全部節點都支持v3接口時,才提高集羣版本到v3。這個只有第一次初始化集羣的時候會遇到,若是集羣已經初始化完畢,再掛掉節點,或者集羣關閉重啓(關閉重啓的時候會從持久化數據中加載集羣API版本),都不會有影響。
v2 quorum=true 的時候,讀取是經過raft進行的,經過cli請求,該參數默認爲true。 v3 –consistency=「l」 的時候(默認)經過raft讀取,不然讀取本地數據。sdk 代碼裏則是經過是否打開:WithSerializable option 來控制。
一致性讀取的狀況下,每次讀取也須要走一次raft協議,能保證一致性,但性能有損失,若是出現網絡分區,集羣的少數節點是不能提供一致性讀取的。但若是不設置該參數,則是直接從本地的store裏讀取,這樣就損失了一致性。使用的時候須要注意根據應用場景設置這個參數,在一致性和可用性之間進行取捨。
Etcd 默認不會自動compact,須要設置啓動參數,或者經過命令進行compcat,若是變動頻繁建議設置,不然會致使空間和內存的浪費。
10、腦洞時間
自動上次 Elasticsearch 的文章以後,給本身安排了一個做業,每次分析源碼後須要提出幾個發散思惟的想法,開個腦洞。
當前IDE的代碼調用分析追蹤都是經過靜態的代碼分析來追蹤方法調用鏈實現的,對閱讀分析代碼很是有用。但程序若是充分使用CSP或者Actor模型後,都經過消息進行調用,沒有了明確的方法調用鏈,給閱讀和理解代碼帶來了困難。若是語言或者IDE能支持這樣的消息投遞追蹤分析,那應該很是有用。固然我這個只是腦洞,不考慮實現的可能性和複雜度。
當前 Etcd 的raft實現保證了多個節點數據之間的同步,但明顯的一個問題就是擴充節點不能解決容量問題。要想解決容量問題,只能進行分片,但分片後如何使用raft同步數據?只能實現一個 multiple group raft,每一個分片的多個副本組成一個虛擬的raft group,經過raft實現數據同步。當前實現了multiple group raft的有 TiKV 和 Cockroachdb,但還沒有一個獨立通用的。理論上來講,若是有了這套 multiple group raft,後面掛個持久化的kv就是一個分佈式kv存儲,掛個內存kv就是分佈式緩存,掛個lucene就是分佈式搜索引擎。固然這只是理論上,要真實現複雜度仍是不小。
11、Etcd 的開源產品啓示
Etcd在Zookeeper已經奠基江湖地位的狀況下,硬是從新造了一個輪子,而且在生態圈中取得了一席之地。一方面能夠看出是社區的形態在變化,溝通機制和對用戶反饋的響應愈來愈重要,另一方面也能夠看出一個項目的易用的重要性有時候甚至高於穩定性和功能。新的算法,新的語言都會給從新制造輪子帶來了機會。
12、gitchat交流羣的問答
答:v2的大多數功能,用v3都能實現,好比用prefix模擬原來的目錄結構,用txn模擬CAS,通常不會有什麼問題。但由於v2和v3的數據是互相隔離的,因此遷移起來略麻煩。建議先在業務中封裝一層,將etcd v2,v3的差別封裝起來,而後經過開關切換。
答:metad的watch實現的比較簡單,由於metad的watch返回的不是變動事件,而是最新的結果。因此metad只維護了一個全局版本號,只要發現客戶端watch的版本小於等於全局版本號,就直接返回最新結果。
etcd和zk兩者大多數狀況下能夠互相替代,都是通用的分佈式一致性kv存儲。兩者之間選擇建議選擇本身的開發棧比較接近而且團隊成員比較熟悉的,好比一種是按語言選擇,go語言的項目用etcd,java的用zk,出問題要看源碼也容易些。若是是新項目,糾結於兩者,那能夠分裝一層lib,相似於docker/libkv,同時支持兩種,有須要能夠切換。
答:etcd和zk的選型前面講到了,兩者的定位都是通用的一致性kv存儲,而eureka和consul的定位則是專作服務註冊和發現。前兩者的優點固然是通用性,應用普遍,部署運維的時候容易和已有的服務一塊兒共用,而同時缺點也是太通用了,每一個應用的服務註冊都有本身的一套元數據格式,互相整合起來就比較麻煩了,好比想作個通用的api gateway就會遇到元數據格式兼容問題。這也成爲後兩者的優點。同時由於後兩者的目標比較具體,因此能夠作一些更高級的功能,好比consul的DNS支持,consul-template工具,eureka的事件訂閱過濾機制。Eureka自己的實現是一個AP系統,也就是說犧牲了一致性,它認爲在服務發現和配置中心這個場景下,可用性和分區容錯比一致性更重要。 我我的其實更期待後兩者的這種專門的解決方案,要是能造成服務註冊標準,那之後應用之間互相交互就容易了。但也有個多是這種標準由集羣調度系統來造成事實標準。
後兩者我瞭解的也不深刻,感受能夠另起一篇文章了。
答:這個坑的概念比較太普遍了,更詳細的能夠翻bug列表。但使用中的大多數坑通常有幾種:
誤用致使的坑。要先認識清楚etcd,zk的定位,它須要保存的是整個集羣共享的信息,不能當存儲用。好比有人在某個zk的某個數據節點下建立了大量的子節點,而後獲取,致使zk報錯,zk的buffer有個4mb的限制,超過就會報錯。
運維方面的坑。etcd,zk這種服務,通常都比較穩定,搭建好後都不用管,但萬一某些節點出問題了,要增長節點恢復系統的時候,可能沒有預案或者操做經驗,致使弄壞集羣。
網絡分區以及可用性設計的坑。設計系統的時候,要想清楚若是etcd或zk整個掛了,或者出現網絡分區,應用的一部分節點只能鏈接到少數派的etcd/zk(少數派不可用)的時候,應用會有什麼表現。這種狀況下,應用的正確表現應該是服務正常運做,但不支持變動,等etcd/zk集羣恢復後就自動恢復了。但若是設計不當,有自動化的一些行爲,可能帶來的故障就大了。
想要少踩坑,一個辦法就是我文中提到的,研究原理知其然同時知其因此然,另一個問題就是多試驗,出了問題有預案。
咱們實現了基於Arm的分佈式互聯的硬件集羣(方法參考的是https://edcashin.wordpress.com/2013/12/29/trying-etcd-on-android-mac-and-raspberry-pi/comment-page-1/ 將etcd跑在Arm開發板上),將Etcd看成一個分佈式的數據庫使用(可是Etcd自己運行在這些硬件之上),而後參考go-rpiohttps://github.com/stianeikeland/go-rpio 實現基於etcd的key-value同步硬件的信息,控制某些GPIO。
問題1:目前已知Etcd能夠爲別的服務提供服務發現,在這個場景下假設已經存在5個運行Etcd節點的硬件,當一個新的Etcd硬件節點被安裝時,Etcd可否爲本身提供服務發現服務,實現Etcd節點的自動發現與加入?
問題2:隨着硬件安裝規模的增長,Etcd的極限是多少,raft是否會由於節點的變多,心跳包的往返而致使同步一次的等待時間變長?
問題3:當規模足夠大,發生網絡分區時,是否分區較小的一批硬件之間的數據是沒法完成同步的?
答:這個案例挺有意思,我一個一個回答。
etcd原本是作服務發現的,若是etcd集羣也須要服務發現,那就再來一個etcd集羣 :)。你能夠本身搭建一個etcd cluster或者用etcd官方提供的 discovery.etcd.io。詳細參看:etcd 官方的 op-guide/clustering
etcd的機制是多節點一致的,因此它的極限有兩部分,一是單機的容量限制,內存和磁盤。二是網絡開銷,每次raft操做須要全部節點參與,節點越多性能越低。因此擴展不少etcd節點是沒有意義的,通常是 3,5,7,9。再多感受就沒意義了。若是大家不太在乎一致性,建議讀請求能夠不經過一致性協議,直接讀取節點本地數據。具體方式文中有說明。
etcd網絡分區時,少數派是不可用狀態,不支持raft請求,但支持非一致性讀請求。
答:這個要看跨機房的場景。若是是徹底無關聯須要公網鏈接的兩個機房,服務之間通常也不須要共享數據吧?部署兩套互不相干的etcd,各用各的比較合適。但若是是相似於aws的可用區的概念,兩個機房內網互通,搭建兩套集羣爲了不機房故障,能夠隨時切換。這個etcd當前沒有太好的解決辦法,建議的辦法是跨可用區部署一個etcd cluster,調整心跳以及選舉超時時間,這個辦法若是有3個可用區機房,每一個機房3個節點,掛任何一個機房都不影響整個集羣,但兩個機房就比較尷尬。還有個辦法是兩個集羣之間同步,這個etcdv3提供了一個mirror的工具,但仍是不太完善,不過感受用etcd的watch機制作一個同步工具也不難。這個機制consul卻是提供了,多數據中心的集羣數據同步,互相不影響可用性。
喜歡的點點關注,點點贊。
對Java技術,架構技術感興趣的同窗,歡迎加QQ羣668041364,一塊兒學習,相互討論。
羣內已經有小夥伴將知識體系整理好(源碼,筆記,PPT,學習視頻),歡迎加羣領取。
關於