etcd作為一個受到ZooKeeper與doozer啟發而催生的項目,除了擁有與之類似的功能外,更專注於以下四點。
- 簡單:基於HTTP+JSON的API讓你用curl就可以輕松使用。
- 安全:可選SSL客戶認證機制。
- 快速:每個實例每秒支持一千次寫操作。
- 可信:使用Raft算法充分實現了分布式。
分布式系統中的數據分為控制數據和應用數據。etcd的使用場景默認處理的數據都是控制數據,對於應用數據,只推薦數據量很小,但是更新訪問頻繁的情況。
應用場景有如下幾類:
場景一:服務發現(Service Discovery)
場景二:消息發布與訂閱
場景三:負載均衡
場景四:分布式通知與協調
場景五:分布式鎖、分布式隊列
場景六:集群監控與Leader競選
舉個最簡單的例子,如果你需要一個分布式存儲倉庫來存儲配置信息,並且希望這個倉庫讀寫速度快、支持高可用、部署簡單、支持http接口,那么就可以使用etcd。
目前,cloudfoundry使用etcd作為hm9000的應用狀態信息存儲,kubernetes用etcd來存儲docker集群的配置信息等。
更為詳盡的內容可閱讀etcd:從應用場景到實現原理的全方位解讀一文。
http://dockone.io/question/115
1. ETCD是什么
ETCD是用於共享配置和服務發現的分布式,一致性的KV存儲系統。該項目目前最新穩定版本為2.3.0. 具體信息請參考[項目首頁]和[Github]。ETCD是CoreOS公司發起的一個開源項目,授權協議為Apache。
提供配置共享和服務發現的系統比較多,其中最為大家熟知的是[Zookeeper](后文簡稱ZK),而ETCD可以算得上是后起之秀了。在項目實現,一致性協議易理解性,運維,安全等多個維度上,ETCD相比Zookeeper都占據優勢。
2. ETCD vs ZK
本文選取ZK作為典型代表與ETCD進行比較,而不考慮[Consul]項目作為比較對象,原因為Consul的可靠性和穩定性還需要時間來驗證(項目發起方自身服務並未使用Consul, 自己都不用)。
- 一致性協議: ETCD使用[Raft]協議, ZK使用ZAB(類PAXOS協議),前者容易理解,方便工程實現;
- 運維方面:ETCD方便運維,ZK難以運維;
- 項目活躍度:ETCD社區與開發活躍,ZK已經快死了;
- API:ETCD提供HTTP+JSON, gRPC接口,跨平台跨語言,ZK需要使用其客戶端;
- 訪問安全方面:ETCD支持HTTPS訪問,ZK在這方面缺失;
3. ETCD的使用場景
和ZK類似,ETCD有很多使用場景,包括:
- 配置管理
- 服務注冊於發現
- 選主
- 應用調度
- 分布式隊列
- 分布式鎖
4. ETCD讀寫性能
按照官網給出的[Benchmark], 在2CPU,1.8G內存,SSD磁盤這樣的配置下,單節點的寫性能可以達到16K QPS, 而先寫后讀也能達到12K QPS。這個性能還是相當可觀的。
5. ETCD工作原理
ETCD使用Raft協議來維護集群內各個節點狀態的一致性。簡單說,ETCD集群是一個分布式系統,由多個節點相互通信構成整體對外服務,每個節點都存儲了完整的數據,並且通過Raft協議保證每個節點維護的數據是一致的。
如圖所示,每個ETCD節點都維護了一個狀態機,並且,任意時刻至多存在一個有效的主節點。主節點處理所有來自客戶端寫操作,通過Raft協議保證寫操作對狀態機的改動會可靠的同步到其他節點。
ETCD工作原理核心部分在於Raft協議。本節接下來將簡要介紹Raft協議,具體細節請參考其[論文]。
Raft協議正如論文所述,確實方便理解。主要分為三個部分:選主,日志復制,安全性。
5.1 選主
Raft協議是用於維護一組服務節點數據一致性的協議。這一組服務節點構成一個集群,並且有一個主節點來對外提供服務。當集群初始化,或者主節點掛掉后,面臨一個選主問題。集群中每個節點,任意時刻處於Leader, Follower, Candidate這三個角色之一。選舉特點如下:
- 當集群初始化時候,每個節點都是Follower角色;
- 集群中存在至多1個有效的主節點,通過心跳與其他節點同步數據;
- 當Follower在一定時間內沒有收到來自主節點的心跳,會將自己角色改變為Candidate,並發起一次選主投票;當收到包括自己在內超過半數節點贊成后,選舉成功;當收到票數不足半數選舉失敗,或者選舉超時。若本輪未選出主節點,將進行下一輪選舉(出現這種情況,是由於多個節點同時選舉,所有節點均為獲得過半選票)。
-
Candidate節點收到來自主節點的信息后,會立即終止選舉過程,進入Follower角色。
為了避免陷入選主失敗循環,每個節點未收到心跳發起選舉的時間是一定范圍內的隨機值,這樣能夠避免2個節點同時發起選主。
5.2 日志復制
所謂日志復制,是指主節點將每次操作形成日志條目,並持久化到本地磁盤,然后通過網絡IO發送給其他節點。其他節點根據日志的邏輯時鍾(TERM)和日志編號(INDEX)來判斷是否將該日志記錄持久化到本地。當主節點收到包括自己在內超過半數節點成功返回,那么認為該日志是可提交的(committed),並將日志輸入到狀態機,將結果返回給客戶端。
這里需要注意的是,每次選主都會形成一個唯一的TERM編號,相當於邏輯時鍾。每一條日志都有全局唯一的編號。
主節點通過網絡IO向其他節點追加日志。若某節點收到日志追加的消息,首先判斷該日志的TERM是否過期,以及該日志條目的INDEX是否比當前以及提交的日志的INDEX跟早。若已過期,或者比提交的日志更早,那么就拒絕追加,並返回該節點當前的已提交的日志的編號。否則,將日志追加,並返回成功。
當主節點收到其他節點關於日志追加的回復后,若發現有拒絕,則根據該節點返回的已提交日志編號,發生其編號下一條日志。
主節點像其他節點同步日志,還作了擁塞控制。具體地說,主節點發現日志復制的目標節點拒絕了某次日志追加消息,將進入日志探測階段,一條一條發送日志,直到目標節點接受日志,然后進入快速復制階段,可進行批量日志追加。
按照日志復制的邏輯,我們可以看到,集群中慢節點不影響整個集群的性能。另外一個特點是,數據只從主節點復制到Follower節點,這樣大大簡化了邏輯流程。
5.3 安全性
截止此刻,選主以及日志復制並不能保證節點間數據一致。試想,當一個某個節點掛掉了,一段時間后再次重啟,並當選為主節點。而在其掛掉這段時間內,集群若有超過半數節點存活,集群會正常工作,那么會有日志提交。這些提交的日志無法傳遞給掛掉的節點。當掛掉的節點再次當選主節點,它將缺失部分已提交的日志。在這樣場景下,按Raft協議,它將自己日志復制給其他節點,會將集群已經提交的日志給覆蓋掉。
這顯然是不可接受的。
其他協議解決這個問題的辦法是,新當選的主節點會詢問其他節點,和自己數據對比,確定出集群已提交數據,然后將缺失的數據同步過來。這個方案有明顯缺陷,增加了集群恢復服務的時間(集群在選舉階段不可服務),並且增加了協議的復雜度。
Raft解決的辦法是,在選主邏輯中,對能夠成為主的節點加以限制,確保選出的節點已定包含了集群已經提交的所有日志。如果新選出的主節點已經包含了集群所有提交的日志,那就不需要從和其他節點比對數據了。簡化了流程,縮短了集群恢復服務的時間。
這里存在一個問題,加以這樣限制之后,還能否選出主呢?答案是:只要仍然有超過半數節點存活,這樣的主一定能夠選出。因為已經提交的日志必然被集群中超過半數節點持久化,顯然前一個主節點提交的最后一條日志也被集群中大部分節點持久化。當主節點掛掉后,集群中仍有大部分節點存活,那這存活的節點中一定存在一個節點包含了已經提交的日志了。
至此,關於Raft協議的簡介就全部結束了。
6. ETCD使用案例
據公開資料顯示,至少有CoreOS, Google Kubernetes, Cloud Foundry, 以及在Github上超過500個項目在使用ETCD。
7. ETCD接口
ETCD提供HTTP協議,在最新版本中支持Google gRPC方式訪問。具體支持接口情況如下:
- ETCD是一個高可靠的KV存儲系統,支持PUT/GET/DELETE接口;
- 為了支持服務注冊與發現,支持WATCH接口(通過http long poll實現);
- 支持KEY持有TTL屬性;
- CAS(compare and swap)操作;
- 支持多key的事務操作;
- 支持目錄操作
8. 結束
本文對ETCD作了一個簡單的介紹,希望對你有幫助。
https://yq.aliyun.com/articles/11035
ETCD系列之二:部署集群
1. 概述
想必很多人都知道ZooKeeper,通常用作配置共享和服務發現。和它類似,ETCD算是一個非常優秀的后起之秀了。本文重點不在描述他們之間的不同點。首先,看看其官網關於ETCD的描述1:
A distributed, reliable key-value store for the most critical data of a distributed system.
在雲計算大行其道的今天,ETCD有很多典型的使用場景。常言道,熟悉一個系統先從部署開始。本文接下來將描述,如何部署ETCD集群。
安裝官網說明文檔,提供了3種集群啟動方式,實際上按照其實現原理分為2類:
- 通過靜態配置方式啟動
- 通過服務發現方式啟動
在部署集群之前,我們需要考慮集群需要配置多少個節點。這是一個重要的考量,不得忽略。
2. 集群節點數量與網絡分割
ETCD使用RAFT協議保證各個節點之間的狀態一致。根據RAFT算法原理,節點數目越多,會降低集群的寫性能。這是因為每一次寫操作,需要集群中大多數節點將日志落盤成功后,Leader節點才能將修改內部狀態機,並返回將結果返回給客戶端。
也就是說在等同配置下,節點數越少,集群性能越好。顯然,只部署1個節點是沒什么意義的。通常,按照需求將集群節點部署為3,5,7,9個節點。
這里能選擇偶數個節點嗎? 最好不要這樣。原因有二:
- 偶數個節點集群不可用風險更高,表現在選主過程中,有較大概率或等額選票,從而觸發下一輪選舉。
- 偶數個節點集群在某些網絡分割的場景下無法正常工作。試想,當網絡分割發生后,將集群節點對半分割開。此時集群將無法工作。按照RAFT協議,此時集群寫操作無法使得大多數節點同意,從而導致寫失敗,集群無法正常工作。
當網絡分割后,ETCD集群如何處理的呢?
- 當集群的Leader在多數節點這一側時,集群仍可以正常工作。少數節點那一側無法收到Leader心跳,也無法完成選舉。
- 當集群的Leader在少數節點這一側時,集群仍可以正常工作,多數派的節點能夠選出新的Leader, 集群服務正常進行。
當網絡分割恢復后,少數派的節點會接受集群Leader的日志,直到和其他節點狀態一致。
3. ETCD參數說明
這里只列舉一些重要的參數,以及其用途。
- —data-dir 指定節點的數據存儲目錄,這些數據包括節點ID,集群ID,集群初始化配置,Snapshot文件,若未指定—wal-dir,還會存儲WAL文件;
- —wal-dir 指定節點的was文件的存儲目錄,若指定了該參數,wal文件會和其他數據文件分開存儲。
- —name 節點名稱
- —initial-advertise-peer-urls 告知集群其他節點url.
- — listen-peer-urls 監聽URL,用於與其他節點通訊
- — advertise-client-urls 告知客戶端url, 也就是服務的url
- — initial-cluster-token 集群的ID
- — initial-cluster 集群中所有節點
4. 通過靜態配置方式啟動ETCD集群
按照官網中的文檔,即可完成集群啟動。這里略。
5. 通過服務發現方式啟動ETCD集群
ETCD還提供了另外一種啟動方式,即通過服務發現的方式啟動。這種啟動方式,依賴另外一個ETCD集群,在該集群中創建一個目錄,並在該目錄中創建一個_config
的子目錄,並且在該子目錄中增加一個size
節點,指定集群的節點數目。
在這種情況下,將該目錄在ETCD中的URL作為節點的啟動參數,即可完成集群啟動。使用--discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
配置項取代靜態配置方式中的--initial-cluster
和inital-cluster-state
參數。其中https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
是在依賴etcd中創建好的目錄url。
6. 節點遷移
在生產環境中,不可避免遇到機器硬件故障。當遇到硬件故障發生的時候,我們需要快速恢復節點。ETCD集群可以做到在不丟失數據的,並且不改變節點ID的情況下,遷移節點。
具體辦法是:
- 1)停止待遷移節點上的etc進程;
- 2)將數據目錄打包復制到新的節點;
- 3)更新該節點對應集群中peer url,讓其指向新的節點;
- 4)使用相同的配置,在新的節點上啟動etcd進程;
5. 結束
本文記錄了ETCD集群啟動的一些注意事項,希望對你有幫助。
https://yq.aliyun.com/articles/29897?spm=5176.100239.blogcont11035.15.7bihps
ETCD系列之三:網絡層實現
1. 概述
在理清ETCD的各個模塊的實現細節后,方便線上運維,理解各種參數組合的意義。本文先從網絡層入手,后續文章會依次介紹各個模塊的實現。
本文將着重介紹ETCD服務的網絡層實現細節。在目前的實現中,ETCD通過HTTP協議對外提供服務,同樣通過HTTP協議實現集群節點間數據交互。
網絡層的主要功能是實現了服務器與客戶端(能發出HTTP請求的各種程序)消息交互,以及集群內部各節點之間的消息交互。
2. ETCD-SERVER整體架構
ETCD-SERVER 大體上可以分為網絡層,Raft模塊,復制狀態機,存儲模塊,架構圖如圖1所示。
圖1 ETCD-SERVER架構圖
- 網絡層:提供網絡數據讀寫功能,監聽服務端口,完成集群節點之間數據通信,收發客戶端數據;
- Raft模塊:完整實現了Raft協議;
- 存儲模塊:KV存儲,WAL文件,SNAPSHOT管理
- 復制狀態機:這個是一個抽象的模塊,狀態機的數據維護在內存中,定期持久化到磁盤,每次寫請求會持久化到WAL文件,並根據寫請求的內容修改狀態機數據。 ## 3. 節點之間網絡拓撲結構 ETCD集群的各個節點之間需要通過HTTP協議來傳遞數據,表現在:
- Leader 向Follower發送心跳包, Follower向Leader回復消息;
- Leader向Follower發送日志追加信息;
- Leader向Follower發送Snapshot數據;
- Candidate節點發起選舉,向其他節點發起投票請求;
- Follower將收的寫操作轉發給Leader;
各個節點在任何時候都有可能變成Leader, Follower, Candidate等角色,同時為了減少創建鏈接開銷,ETCD節點在啟動之初就創建了和集群其他節點之間的鏈接。
因此,ETCD集群節點之間的網絡拓撲是一個任意2個節點之間均有長鏈接相互連接的網狀結構。如圖2所示。
圖2 ETCD集群節點網絡拓撲圖
需要注意的是,每一個節點都會創建到其他各個節點之間的長鏈接。每個節點會向其他節點宣告自己監聽的端口,該端口只接受來自其他節點創建鏈接的請求。
4. 節點之間消息交互
在ETCD實現中,根據不同用途,定義了各種不同的消息類型。各種不同的消息,最終都通過google protocol buffer協議進行封裝。這些消息攜帶的數據大小可能不盡相同。例如 傳輸SNAPSHOT數據的消息數據量就比較大,甚至超過1GB, 而leader到follower節點之間的心跳消息可能只有幾十個字節。
因此,網絡層必須能夠高效地處理不同數據量的消息。ETCD在實現中,對這些消息采取了分類處理,抽象出了2種類型消息傳輸通道:Stream類型通道和Pipeline類型通道。這兩種消息傳輸通道都使用HTTP協議傳輸數據。
圖3 節點之間建立消息傳輸通道
集群啟動之初,就創建了這兩種傳輸通道,各自特點:
- Stream類型通道:點到點之間維護HTTP長鏈接,主要用於傳輸數據量較小的消息,例如追加日志,心跳等;
- Pipeline類型通道:點到點之間不維護HTTP長鏈接,短鏈接傳輸數據,用完即關閉。用於傳輸數據量大的消息,例如snapshot數據。
如果非要做做一個類別的話,Stream就向點與點之間維護了雙向傳輸帶,消息打包后,放到傳輸帶上,傳到對方,對方將回復消息打包放到反向傳輸帶上;而Pipeline就像擁有N輛汽車,大消息打包放到汽車上,開到對端,然后在回來,最多可以同時發送N個消息。
Stream類型通道
Stream類型通道處理數據量少的消息,例如心跳,日志追加消息。點到點之間只維護1個HTTP長鏈接,交替向鏈接中寫入數據,讀取數據。
Stream 類型通道是節點啟動后主動與其他每一個節點建立。Stream類型通道通過Channel 與Raft模塊傳遞消息。每一個Stream類型通道關聯2個Goroutines, 其中一個用於建立HTTP鏈接,並從鏈接上讀取數據, decode成message, 通過Channel傳給Raft模塊中,另外一個通過Channel 從Raft模塊中收取消息,然后寫入通道。
具體點,ETCD使用golang的http包實現Stream類型通道:
- 1)被動發起方監聽端口, 並在對應的url上掛載相應的handler(當前請求來領時,handler的ServeHTTP方法會被調用)
- 2)主動發起方發送HTTP GET請求;
- 3)監聽方的Handler的ServeHTTP訪問被調用(框架層傳入http.ResponseWriter和http.Request對象),其中http.ResponseWriter對象作為參數傳入Writter-Goroutine(就這么稱呼吧),該Goroutine的主循環就是將Raft模塊傳出的message寫入到這個responseWriter對象里;http.Request的成員變量Body傳入到Reader-Gorouting(就這么稱呼吧),該Gorutine的主循環就是不斷讀取Body上的數據,decode成message 通過Channel傳給Raft模塊。
Pipeline類型通道
Pipeline類型通道處理數量大消息,例如SNAPSHOT消息。這種類型消息需要和心跳等消息分開處理,否則會阻塞心跳。
Pipeline類型通道也可以傳輸小數據量的消息,當且僅當Stream類型鏈接不可用時。
Pipeline類型通道可用並行發出多個消息,維護一組Goroutines, 每一個Goroutines都可向對端發出POST請求(攜帶數據),收到回復后,鏈接關閉。
具體地,ETCD使用golang的http包實現的:
- 1)根據參數配置,啟動N個Goroutines;
- 2)每一個Goroutines的主循環阻塞在消息Channel上,當收到消息后,通過POST請求發出數據,並等待回復。
5. 網絡層與Raft模塊之間的交互
在ETCD中,Raft協議被抽象為Raft模塊。按照Raft協議,節點之間需要交互數據。在ETCD中,通過Raft模塊中抽象的RaftNode擁有一個message box, RaftNode將各種類型消息放入到messagebox中,有專門Goroutine將box里的消息寫入管道,而管道的另外一端就鏈接在網絡層的不同類型的傳輸通道上,有專門的Goroutine在等待(select)。
而網絡層收到的消息,也通過管道傳給RaftNode。RaftNode中有專門的Goroutine在等待消息。
也就是說,網絡層與Raft模塊之間通過Golang Channel完成數據通信。這個比較容易理解。
6 ETCD-SERVER處理請求(與客戶端的信息交互)
在ETCD-SERVER啟動之初,會監聽服務端口,當服務端口收到請求后,解析出message后,通過管道傳入給Raft模塊,當Raft模塊按照Raft協議完成操作后,回復該請求(或者請求超時關閉了)。
7 主要數據結構
網絡層抽象為Transport類,該類完成網絡數據收發。對Raft模塊提供Send/SendSnapshot接口,提供數據讀寫的Channel,對外監聽指定端口。
8. 結束
本文整理了ETCD節點網絡層的實現,為分析其他模塊打下基礎。
https://yq.aliyun.com/articles/29900?spm=5176.100239.blogcont29897.12.4qqzpF