前言
隨着kubernetes項目的日益火熱,該項目中用到的etcd組件作爲一個高可用強一致性的服務發現存儲倉庫,漸漸的被開發人員所關注。
在雲計算時代,如何讓服務快速、透明的接入到計算集羣中,如何讓共享配置信息快速被集羣中的所有節點發現,如何構建一套高可用、安全、易於部署以及快速響應的服務集羣成爲了需要解決的問題。
Etcd爲解決這類問題帶來便捷。
官方地址: https://coreos.com/etcd/
項目地址: https://github.com/coreos/etcd
Etcd是什麼
Etcd是一個高可用的鍵值存儲系統,主要用於共享配置和服務發現,它通過Raft一致性算法處理日誌複製以保證強一致性,我們可以理解它爲一個高可用強一致性的服務發現存儲倉庫。
在kubernetes集羣中,etcd主要用於配置共享和服務發現
Etcd主要解決的是分佈式系統中數據一致性的問題,而分佈式系統中的數據分爲控制數據和應用數據,etcd處理的數據類型爲控制數據,對於很少量的應用數據也可以進行處理。
Etcd和Zookeeper的比較
Zookeeper有如下缺點
1.複雜。ZooKeeper的部署維護複雜,管理員需要掌握一系列的知識和技能;而Paxos強一致性算法也是素來以複雜難懂而聞名於世(ETCD使用[Raft]協議, ZK使用ZAB,類PAXOS協議);另外,ZooKeeper的使用也比較複雜,需要安裝客戶端,官方只提供了Java和C兩種語言的接口。
2.Java編寫。這裏不是對Java有偏見,而是Java本身就偏向於重型應用,它會引入大量的依賴。而運維人員則普遍希望保持強一致、高可用的機器集羣儘可能簡單,維護起來也不易出錯。
3.發展緩慢。Apache基金會項目特有的“Apache Way”在開源界飽受爭議,其中一大原因就是由於基金會龐大的結構以及鬆散的管理導致項目發展緩慢。
相較之下,Etcd
1.簡單。使用Go語言編寫部署簡單;使用HTTP作爲接口使用簡單;使用Raft算法保證強一致性讓用戶易於理解。
2.數據持久化。etcd默認數據一更新就進行持久化。
3.安全。etcd支持SSL客戶端安全認證。
Etcd的架構與術語
流程分析
通常一個用戶的請求發送過來,會經過HTTP Server轉發給Store進行具體的事務處理,如果涉及到節點的修改,則需要交給Raft模塊進行狀態的變更,日誌的記錄。
然後再同步給別的etcd節點確認數據提交,最後進行數據提交,再次同步。
工作原理
Etcd使用Raft協議來維護集羣內各個節點狀態的一致性。簡單說,ETCD集羣是一個分佈式系統,由多個節點相互通信構成整體對外服務,每個節點都存儲了完整的數據,並且通過Raft協議保證每個節點維護的數據是一致的。
Etcd主要分爲四個部分
HTTP Server: 用於處理用戶發送的API請求以及其他etcd節點的同步與心跳信息請求
Store: 用於處理 etcd 支持的各類功能的事務,包括數據索引、節點狀態變更、監控與反饋、事件處理與執行等等,是 etcd 對用戶提供的大多數 API 功能的具體實現。
Raft: Raft 強一致性算法的具體實現,是 etcd 的核心。
WAL:Write Ahead Log(預寫式日誌/日誌先行),是 etcd 的數據存儲方式,也是一種實現事務日誌的標準方法。etcd通過 WAL 進行持久化存儲,所有的數據提交前都會事先記錄日誌。Snapshot 是爲了防止數據過多而進行的狀態快照;Entry 表示存儲的具體日誌內容。
服務發現
服務發現要解決的也是分佈式系統中最常見的問題之一,即在同一個分佈式集羣中的進程或服務,要如何才能找到對方並建立連接。本質上來說,服務發現就是想要了解集羣中是否有進程在監聽 udp 或 tcp 端口,並且通過名字就可以查找和連接。要解決服務發現的問題,需要具備以下三點:
1.一個強一致性、高可用的服務存儲目錄。基於 Raft 算法的 etcd 天生就是這樣一個強一致性高可用的服務存儲目錄。
2.一種註冊服務和監控服務健康狀態的機制。用戶可以在 etcd 中註冊服務,並且對註冊的服務設置key TTL,定時保持服務的心跳以達到監控健康狀態的效果。
3.一種查找和連接服務的機制。通過在 etcd 指定的主題下注冊的服務也能在對應的主題下查找到。爲了確保連接,我們可以在每個服務機器上都部署一個 Proxy 模式的 etcd,這樣就可以確保能訪問 etcd 集羣的服務都能互相連接。
例如隨着 Docker 容器的流行,多種微服務共同協作,構成一個相對功能強大的架構的案例越來越多。透明化的動態添加這些服務的需求也日益強烈。通過服務發現機制,在 etcd 中註冊某個服務名字的目錄,在該目錄下存儲可用的服務節點的 IP。在使用服務的過程中,只要從服務目錄下查找可用的服務節點去使用即可。
Etcd集羣中的術語
Raft: etcd所採用的保證分佈式系統強一致的算法
Node: 一個Raft狀態機實例
Member: 一個etcd實例,管理一個Node,可以爲客戶端請求提供服務
Cluster: 多個Member構成的可以協同工作的etcd集羣
Peer: 同一個集羣中,其他Member的稱呼
Client: 向etcd集羣發送HTTP請求的客戶端
WAL: 預寫日誌,是etcd用於持久化存儲的日誌格式
Snapshot: etcd防止WAL文件過多而設置的快照,存儲etcd數據狀態
Proxy: etcd的一種模式,可以爲etcd提供反向代理服務
Leader: Raft算法中通過競選而產生的處理所有數據提交的節點
Follower: Raft算法中競選失敗的節點,作爲從屬節點,爲算法提供強一致性保證
Candidate: Follower超過一定時間接收不到Leader節點的心跳的時候,會轉變爲Candidate(候選者)開始Leader競選
Term: 某個節點稱爲Leader到下一次競選開始的時間週期,稱爲Term(任界,任期)
Index: 數據項編號, Raft中通過Term和Index來定位數據
Raft算法
Raft 是一種爲了管理複製日誌的一致性算法。它提供了和 Paxos 算法相同的功能和性能,但是它的算法結構和 Paxos 不同,使得 Raft 算法更加容易理解並且更容易構建實際的系統。一致性算法允許一組機器像一個整體一樣工作,即使其中一些機器出現故障也能夠繼續工作下去。正因爲如此,一致性算法在構建可信賴的大規模軟件系統中扮演着重要的角色。
Raft算法分爲三部分
Leader選舉、日誌複製和安全性
Raft算法特性:
1.強領導者: 和其他一致性算法相比,Raft 使用一種更強的領導能力形式。比如,日誌條目只從領導者發送給其他的服務器。這種方式簡化了對複製日誌的管理並且使得 Raft 算法更加易於理解。
2.領導選舉: Raft 算法使用一個隨機計時器來選舉領導者。這種方式只是在任何一致性算法都必須實現的心跳機制上增加了一點機制。在解決衝突的時候會更加簡單快捷。
3.成員關係調整: Raft 使用一種共同一致的方法來處理集羣成員變換的問題,在這種方法下,處於調整過程中的兩種不同的配置集羣中大多數機器會有重疊,這就使得集羣在成員變換的時候依然可以繼續工作。
Leader選舉
Raft 狀態機
Raft集羣中的每個節點都處於一種基於角色的狀態機中。具體來說,Raft定義了節點的三種角色: Follower、Candidate和Leader。
1.Leader(領導者): Leader節點在集羣中有且僅能有一個,它負責向所有的Follower節點同步日誌數據
2.Follower(跟隨者): Follower節點從Leader節點獲取日誌,提供數據查詢功能,並將所有修改請求轉發給Leader節點
3.Candidate(候選者): 當集羣中的Leader節點不存在或者失聯之後,其他Follower節點轉換爲Candidate,然後開始新的Leader節點選舉
這三種角色狀態之間的轉換,如下圖:
一個 Raft 集羣包含若干個服務器節點;通常是 5 個,這允許整個系統容忍 2 個節點的失效。在任何時刻,每一個服務器節點都處於這三個狀態之一:領導人、跟隨者或者候選人。在通常情況下,系統中只有一個領導人並且其他的節點全部都是跟隨者。跟隨者都是被動的:他們不會發送任何請求,只是簡單的響應來自領導者或者候選人的請求。領導人處理所有的客戶端請求(如果一個客戶端和跟隨者聯繫,那麼跟隨者會把請求重定向給領導人)
在節點初始啓動的時候,所有節點的Raft狀態機都會處於Follower狀態。當Follower在一定的時間週期內沒有收到來自Leader節點的心跳數據包的時候,節點會將自己的狀態切換爲Candidate,並向集羣中其他Follower節點發送投票請求,Follower都會將自己的票投給收到的第一個投票請求節點。當Candidate收到來自集羣中超過半數節點的投票後,會成爲新的Leader節點。
Leader節點將接受並保存用戶發送的數據,並向其他的Follower節點同步日誌。
Follower只響應來自其他服務器的請求。如果Follower接收不到消息,那麼他就會變成候選人併發起一次選舉。獲得集羣中大多數選票的候選人將成爲Leader。在一個任期(Term)內,領導人一直都會是領導人直到自己宕機了。
Leader節點依靠定時向所有Follower發送心跳數據來保持地位。當急羣衆的Leader節點出現故障的時候,Follower會重新選舉新的節點,保證整個集羣正常運行。
每次成功的選舉,新的Leader的Term(任期)值都會比之前的Leader增加1。當集羣中由於網絡或者其他原因出現分裂後又重新合併的時候,集羣中可能會出現多於一個的Leader節點,此時,Term值更高的節點纔會成爲真正的Leader。
Raft算法中的Term(任期)
關於Term,如下圖:
Raft會把時間分割成任意長度的任期。並且任期用連續的整數來標記。每一段任期都是從一次選舉開始,一個或者多個候選人嘗試成爲領導者。如果一個候選人贏得選舉,然後他就會在接下來的任期中充當Leader的職責。在某些情況下,一次選舉會造成選票瓜分,這樣,這一個任期將沒有Leader。如果沒有Leader,那麼新的一輪選舉就馬上開始,也就是新的任期就會開始。Raft保證了在一個Term任期內,有且只有一個Leader。
日誌複製
所謂日誌複製,是指主節點將每次操作形成日誌條目,並持久化到本地磁盤,然後通過網絡IO發送給其他節點。
一旦一個領導人被選舉出來,他就開始爲客戶端提供服務。客戶端的每一個請求都包含一條被複制狀態機執行的指令。領導人把這條指令作爲一條新的日誌條目附加到日誌中去,然後並行的發起附加條目 RPCs 給其他的服務器,讓他們複製這條日誌條目。
Raft 算法保證所有已提交的日誌條目都是持久化的並且最終會被所有可用的狀態機執行。當主節點收到包括自己在內超過半數節點成功返回,那麼認爲該日誌是可提交的(committed),並將日誌輸入到狀態機,將結果返回給客戶端。
在正常的操作中,領導人和跟隨者的日誌保持一致性,所以附加日誌 RPC 的一致性檢查從來不會失敗。然而,領導人崩潰的情況會使得日誌處於不一致的狀態(老的領導人可能還沒有完全複製所有的日誌條目)。這種不一致問題會在一系列的領導人和跟隨者崩潰的情況下加劇。跟隨者的日誌可能和新的領導人不同的方式。跟隨者可能會丟失一些在新的領導人中有的日誌條目,他也可能擁有一些領導人沒有的日誌條目,或者兩者都發生。丟失或者多出日誌條目可能會持續多個任期。這就引出了另一個部分,就是安全性
安全性
截止此刻,選主以及日誌複製並不能保證節點間數據一致。試想,當一個某個節點掛掉了,一段時間後再次重啓,並當選爲主節點。而在其掛掉這段時間內,集羣若有超過半數節點存活,集羣會正常工作,那麼會有日誌提交。這些提交的日誌無法傳遞給掛掉的節點。當掛掉的節點再次當選主節點,它將缺失部分已提交的日誌。在這樣場景下,按Raft協議,它將自己日誌複製給其他節點,會將集羣已經提交的日誌給覆蓋掉。這顯然是錯誤的
其他協議解決這個問題的辦法是,新當選的主節點會詢問其他節點,和自己數據對比,確定出集羣已提交數據,然後將缺失的數據同步過來。這個方案有明顯缺陷,增加了集羣恢復服務的時間(集羣在選舉階段不可服務),並且增加了協議的複雜度。Raft解決的辦法是,在選主邏輯中,對能夠成爲主的節點加以限制,確保選出的節點已定包含了集羣已經提交的所有日誌。如果新選出的主節點已經包含了集羣所有提交的日誌,那就不需要從和其他節點比對數據了。簡化了流程,縮短了集羣恢復服務的時間。
這裏存在一個問題,加以這樣限制之後,還能否選出主呢?答案是:只要仍然有超過半數節點存活,這樣的主一定能夠選出。因爲已經提交的日誌必然被集羣中超過半數節點持久化,顯然前一個主節點提交的最後一條日誌也被集羣中大部分節點持久化。當主節點掛掉後,集羣中仍有大部分節點存活,那這存活的節點中一定存在一個節點包含了已經提交的日誌了。
Etcd的代理節點(proxy)
Etcd針對Raft的角色模型進行了擴展,增加了Proxy角色。proxy模式的本職就是啓一個HTTP代理服務器,把客戶發到這個服務器的請求轉發給別的 etcd 節點。
作爲Proxy角色的節點不會參與Leader的選舉,只是將所有接收到的用戶查詢和修改請求轉發到任意一個Follower或者Leader節點上。
Proxy節點可以在啓動Etcd的時候通過"--proxy on"參數指定。在使用了"節點自發現"服務的集羣中,可以設置一個固定的"參選節點數目",超過這個數目的成員自動轉換爲Proxy節點。
一旦節點成爲Proxy之後,便不再參與所有Leader選舉和Raft狀態變化。除非將這個節點重啓並指定爲成員的Follower節點
etcd 作爲一個反向代理把客戶的請求轉發給可用的 etcd 集羣。這樣,你就可以在每一臺機器都部署一個 Proxy 模式的 etcd 作爲本地服務,如果這些 etcd Proxy 都能正常運行,那麼你的服務發現必然是穩定可靠的。
完整的Etcd角色狀態轉換過程如下圖: