1. 程式人生 > >etcd: 從應用場景到實現原理的全方位解讀

etcd: 從應用場景到實現原理的全方位解讀

隨著CoreOS和Kubernetes等專案在開源社群日益火熱,它們專案中都用到的etcd元件作為一個高可用強一致性的服務發現儲存倉庫,漸漸為開發人員所關注。在雲端計算時代,如何讓服務快速透明地接入到計算叢集中,如何讓共享配置資訊快速被叢集中的所有機器發現,更為重要的是,如何構建這樣一套高可用、安全、易於部署以及響應快速的服務叢集,已經成為了迫切需要解決的問題。etcd為解決這類問題帶來了福音,本文將從etcd的應用場景開始,深入解讀etcd的實現方式,以供開發者們更為充分地享用etcd所帶來的便利。

經典應用場景

要問etcd是什麼?很多人第一反應可能是一個鍵值儲存倉庫,卻沒有重視官方定義的後半句,用於配置共享和服務發現。

A highly-available key value store for shared configuration and service discovery.

實際上,etcd作為一個受到ZooKeeper與doozer啟發而催生的專案,除了擁有與之類似的功能外,更專注於以下四點。

  • 簡單:基於HTTP JSON的API讓你用curl就可以輕鬆使用。
  • 安全:可選SSL客戶認證機制。
  • 快速:每個例項每秒支援一千次寫操作。
  • 可信:使用Raft演算法充分實現了分散式。

隨著雲端計算的不斷髮展,分散式系統中涉及到的問題越來越受到人們重視。受阿里中介軟體團隊對ZooKeeper典型應用場景一覽

一文的啟發,筆者根據自己的理解也總結了一些etcd的經典使用場景。讓我們來看看etcd這個基於Raft強一致性演算法的分散式儲存倉庫能給我們帶來哪些幫助。

值得注意的是,分散式系統中的資料分為控制資料和應用資料。使用etcd的場景預設處理的資料都是控制資料,對於應用資料,只推薦資料量很小,但是更新訪問頻繁的情況

場景一:服務發現(Service Discovery)

服務發現要解決的也是分散式系統中最常見的問題之一,即在同一個分散式叢集中的程序或服務,要如何才能找到對方並建立連線。本質上來說,服務發現就是想要了解叢集中是否有程序在監聽udp或tcp埠,並且通過名字就可以查詢和連線。要解決服務發現的問題,需要有下面三大支柱,缺一不可。

  1. 一個強一致性、高可用的服務儲存目錄。基於Raft演算法的etcd天生就是這樣一個強一致性高可用的服務儲存目錄。
  2. 一種註冊服務和監控服務健康狀態的機制。使用者可以在etcd中註冊服務,並且對註冊的服務設定 keyTTL,定時保持服務的心跳以達到監控健康狀態的效果。
  3. 一種查詢和連線服務的機制。通過在etcd指定的主題下注冊的服務也能在對應的主題下查詢到。為了確保連線,我們可以在每個服務機器上都部署一個Proxy模式的etcd,這樣就可以確保能訪問etcd叢集的服務都能互相連線。

圖1 服務發現示意圖

下面我們來看服務發現對應的具體場景。

  • 微服務協同工作架構中,服務動態新增。隨著Docker容器的流行,多種微服務共同協作,構成一個相對功能強大的架構的案例越來越多。透明化的動態新增這些服務的需求也日益強烈。通過服務發現機制,在etcd中註冊某個服務名字的目錄,在該目錄下儲存可用的服務節點的IP。在使用服務的過程中,只要從服務目錄下查詢可用的服務節點去使用即可。

圖2 微服務協同工作

  • PaaS平臺中應用多例項與例項故障重啟透明化。PaaS平臺中的應用一般都有多個例項,通過域名,不僅可以透明的對這多個例項進行訪問,而且還可以做到負載均衡。但是應用的某個例項隨時都有可能故障重啟,這時就需要動態的配置域名解析(路由)中的資訊。通過etcd的服務發現功能就可以輕鬆解決這個動態配置的問題。

圖3 雲平臺多例項透明化

場景二:訊息釋出與訂閱

在分散式系統中,最適用的一種元件間通訊方式就是訊息釋出與訂閱。即構建一個配置共享中心,資料提供者在這個配置中心釋出訊息,而訊息使用者則訂閱他們關心的主題,一旦主題有訊息釋出,就會實時通知訂閱者。通過這種方式可以做到分散式系統配置的集中式管理與動態更新。

  • 應用中用到的一些配置資訊放到etcd上進行集中管理。這類場景的使用方式通常是這樣:應用在啟動的時候主動從etcd獲取一次配置資訊,同時,在etcd節點上註冊一個Watcher並等待,以後每次配置有更新的時候,etcd都會實時通知訂閱者,以此達到獲取最新配置資訊的目的。
  • 分散式搜尋服務中,索引的元資訊和伺服器叢集機器的節點狀態存放在etcd中,供各個客戶端訂閱使用。使用etcd的 keyTTL功能可以確保機器狀態是實時更新的。
  • 分散式日誌收集系統。這個系統的核心工作是收集分佈在不同機器的日誌。收集器通常是按照應用(或主題)來分配收集任務單元,因此可以在etcd上建立一個以應用(主題)命名的目錄P,並將這個應用(主題相關)的所有機器ip,以子目錄的形式儲存到目錄P上,然後設定一個etcd遞迴的Watcher,遞迴式的監控應用(主題)目錄下所有資訊的變動。這樣就實現了機器IP(訊息)變動的時候,能夠實時通知到收集器調整任務分配。
  • 系統中資訊需要動態自動獲取與人工干預修改資訊請求內容的情況。通常是暴露出介面,例如JMX介面,來獲取一些執行時的資訊。引入etcd之後,就不用自己實現一套方案了,只要將這些資訊存放到指定的etcd目錄中即可,etcd的這些目錄就可以通過HTTP的介面在外部訪問。

圖4 訊息釋出與訂閱

場景三:負載均衡

場景一中也提到了負載均衡,本文所指的負載均衡均為軟負載均衡。分散式系統中,為了保證服務的高可用以及資料的一致性,通常都會把資料和服務部署多份,以此達到對等服務,即使其中的某一個服務失效了,也不影響使用。由此帶來的壞處是資料寫入效能下降,而好處則是資料訪問時的負載均衡。因為每個對等服務節點上都存有完整的資料,所以使用者的訪問流量就可以分流到不同的機器上。

  • etcd本身分散式架構儲存的資訊訪問支援負載均衡。etcd叢集化以後,每個etcd的核心節點都可以處理使用者的請求。所以,把資料量小但是訪問頻繁的訊息資料直接儲存到etcd中也是個不錯的選擇,如業務系統中常用的二級程式碼表(在表中儲存程式碼,在etcd中儲存程式碼所代表的具體含義,業務系統呼叫查表的過程,就需要查詢表中程式碼的含義)。
  • 利用etcd維護一個負載均衡節點表。etcd可以監控一個叢集中多個節點的狀態,當有一個請求發過來後,可以輪詢式的把請求轉發給存活著的多個狀態。類似KafkaMQ,通過ZooKeeper來維護生產者和消費者的負載均衡。同樣也可以用etcd來做ZooKeeper的工作。

圖5 負載均衡

場景四:分散式通知與協調

這裡說到的分散式通知與協調,與訊息釋出和訂閱有些相似。都用到了etcd中的Watcher機制,通過註冊與非同步通知機制,實現分散式環境下不同系統之間的通知與協調,從而對資料變更做到實時處理。實現方式通常是這樣:不同系統都在etcd上對同一個目錄進行註冊,同時設定Watcher觀測該目錄的變化(如果對子目錄的變化也有需要,可以設定遞迴模式),當某個系統更新了etcd的目錄,那麼設定了Watcher的系統就會收到通知,並作出相應處理。

  • 通過etcd進行低耦合的心跳檢測。檢測系統和被檢測系統通過etcd上某個目錄關聯而非直接關聯起來,這樣可以大大減少系統的耦合性。
  • 通過etcd完成系統排程。某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。管理人員在控制檯作的一些操作,實際上是修改了etcd上某些目錄節點的狀態,而etcd就把這些變化通知給註冊了Watcher的推送系統客戶端,推送系統再作出相應的推送任務。
  • 通過etcd完成工作彙報。大部分類似的任務分發系統,子任務啟動後,到etcd來註冊一個臨時工作目錄,並且定時將自己的進度進行彙報(將進度寫入到這個臨時目錄),這樣任務管理者就能夠實時知道任務進度。

圖6 分散式協同工作

場景五:分散式鎖

因為etcd使用Raft演算法保持了資料的強一致性,某次操作儲存到叢集中的值必然是全域性一致的,所以很容易實現分散式鎖。鎖服務有兩種使用方式,一是保持獨佔,二是控制時序。

  • 保持獨佔即所有獲取鎖的使用者最終只有一個可以得到。etcd為此提供了一套實現分散式鎖原子操作CAS( CompareAndSwap)的API。通過設定 prevExist值,可以保證在多個節點同時去建立某個目錄時,只有一個成功。而建立成功的使用者就可以認為是獲得了鎖。
  • 控制時序,即所有想要獲得鎖的使用者都會被安排執行,但是獲得鎖的順序也是全域性唯一的,同時決定了執行順序。etcd為此也提供了一套API(自動建立有序鍵),對一個目錄建值時指定為 POST動作,這樣etcd會自動在目錄下生成一個當前最大的值為鍵,儲存這個新的值(客戶端編號)。同時還可以使用API按順序列出所有當前目錄下的鍵值。此時這些鍵的值就是客戶端的時序,而這些鍵中儲存的值可以是代表客戶端的編號。

圖7 分散式鎖

場景六:分散式佇列

分散式佇列的常規用法與場景五中所描述的分散式鎖的控制時序用法類似,即建立一個先進先出的佇列,保證順序。

另一種比較有意思的實現是在保證佇列達到某個條件時再統一按順序執行。這種方法的實現可以在/queue這個目錄中另外建立一個/queue/condition節點。

  • condition可以表示佇列大小。比如一個大的任務需要很多小任務就緒的情況下才能執行,每次有一個小任務就緒,就給這個condition數字加1,直到達到大任務規定的數字,再開始執行佇列裡的一系列小任務,最終執行大任務。
  • condition可以表示某個任務在不在佇列。這個任務可以是所有排序任務的首個執行程式,也可以是拓撲結構中沒有依賴的點。通常,必須執行這些任務後才能執行佇列中的其他任務。
  • condition還可以表示其它的一類開始執行任務的通知。可以由控制程式指定,當condition出現變化時,開始執行佇列任務。

圖8 分散式佇列

場景七:叢集監控與Leader競選

通過etcd來進行監控實現起來非常簡單並且實時性強。

  1. 前面幾個場景已經提到Watcher機制,當某個節點消失或有變動時,Watcher會第一時間發現並告知使用者。
  2. 節點可以設定 TTLkey,比如每隔30s傳送一次心跳使代表該機器存活的節點繼續存在,否則節點消失。

這樣就可以第一時間檢測到各節點的健康狀態,以完成叢集的監控要求。

另外,使用分散式鎖,可以完成Leader競選。這種場景通常是一些長時間CPU計算或者使用IO操作的機器,只需要競選出的Leader計算或處理一次,就可以把結果複製給其他的Follower。從而避免重複勞動,節省計算資源。

這個的經典場景是搜尋系統中建立全量索引。如果每個機器都進行一遍索引的建立,不但耗時而且建立索引的一致性不能保證。通過在etcd的CAS機制同時建立一個節點,建立成功的機器作為Leader,進行索引計算,然後把計算結果分發到其它節點。

圖9 Leader競選

場景八:為什麼用etcd而不用ZooKeeper?

閱讀了“ZooKeeper典型應用場景一覽”一文的讀者可能會發現,etcd實現的這些功能,ZooKeeper都能實現。那麼為什麼要用etcd而非直接使用ZooKeeper呢?

相較之下,ZooKeeper有如下缺點:

  1. 複雜。ZooKeeper的部署維護複雜,管理員需要掌握一系列的知識和技能;而Paxos強一致性演算法也是素來以複雜難懂而聞名於世;另外,ZooKeeper的使用也比較複雜,需要安裝客戶端,官方只提供了Java和C兩種語言的介面。
  2. Java編寫。這裡不是對Java有偏見,而是Java本身就偏向於重型應用,它會引入大量的依賴。而運維人員則普遍希望保持強一致、高可用的機器叢集儘可能簡單,維護起來也不易出錯。
  3. 發展緩慢。Apache基金會專案特有的“Apache Way”在開源界飽受爭議,其中一大原因就是由於基金會龐大的結構以及鬆散的管理導致專案發展緩慢。

而etcd作為一個後起之秀,其優點也很明顯。

  1. 簡單。使用Go語言編寫部署簡單;使用HTTP作為介面使用簡單;使用Raft演算法保證強一致性讓使用者易於理解
  2. 資料持久化。etcd預設資料一更新就進行持久化。
  3. 安全。etcd支援SSL客戶端安全認證。

最後,etcd作為一個年輕的專案,真正告訴迭代和開發中,這既是一個優點,也是一個缺點。優點是它的未來具有無限的可能性,缺點是無法得到大專案長時間使用的檢驗。然而,目前CoreOS、Kubernetes和CloudFoundry等知名專案均在生產環境中使用了etcd,所以總的來說,etcd值得你去嘗試。

etcd實現原理解讀

上一節中,我們概括了許多etcd的經典場景,這一節,我們將從etcd的架構開始,深入到原始碼中解析etcd。

1 架構

圖10 etcd架構圖

從etcd的架構圖中我們可以看到,etcd主要分為四個部分。

  • HTTP Server: 用於處理使用者傳送的API請求以及其它etcd節點的同步與心跳資訊請求。
  • Store:用於處理etcd支援的各類功能的事務,包括資料索引、節點狀態變更、監控與反饋、事件處理與執行等等,是etcd對使用者提供的大多數API功能的具體實現。
  • Raft:Raft強一致性演算法的具體實現,是etcd的核心。
  • WAL:Write Ahead Log(預寫式日誌),是etcd的資料儲存方式。除了在記憶體中存有所有資料的狀態以及節點的索引以外,etcd就通過WAL進行持久化儲存。WAL中,所有的資料提交前都會事先記錄日誌。Snapshot是為了防止資料過多而進行的狀態快照;Entry表示儲存的具體日誌內容。

通常,一個使用者的請求傳送過來,會經由HTTP Server轉發給Store進行具體的事務處理,如果涉及到節點的修改,則交給Raft模組進行狀態的變更、日誌的記錄,然後再同步給別的etcd節點以確認資料提交,最後進行資料的提交,再次同步。

2 新版etcd重要變更列表

  • 獲得了IANA認證的埠,2379用於客戶端通訊,2380用於節點通訊,與原先的(4001 peers / 7001 clients)共用。
  • 每個節點可監聽多個廣播地址。監聽的地址由原來的一個擴充套件到多個,使用者可以根據需求實現更加複雜的叢集環境,如一個是公網IP,一個是虛擬機器(容器)之類的私有IP。
  • etcd可以代理訪問leader節點的請求,所以如果你可以訪問任何一個etcd節點,那麼你就可以無視網路的拓撲結構對整個叢集進行讀寫操作。
  • etcd叢集和叢集中的節點都有了自己獨特的ID。這樣就防止出現配置混淆,不是本叢集的其他etcd節點發來的請求將被遮蔽。
  • etcd叢集啟動時的配置資訊目前變為完全固定,這樣有助於使用者正確配置和啟動。
  • 執行時節點變化(Runtime Reconfiguration)。使用者不需要重啟 etcd 服務即可實現對 etcd 叢集結構進行變更。啟動後可以動態變更叢集配置。
  • 重新設計和實現了Raft演算法,使得執行速度更快,更容易理解,包含更多測試程式碼。
  • Raft日誌現在是嚴格的只能向後追加、預寫式日誌系統,並且在每條記錄中都加入了CRC校驗碼。
  • 啟動時使用的_etcd/* 關鍵字不再暴露給使用者
  • 廢棄叢集自動調整功能的standby模式,這個功能使得使用者維護叢集更困難。
  • 新增Proxy模式,不加入到etcd一致性叢集中,純粹進行代理轉發。
  • ETCD_NAME(-name)引數目前是可選的,不再用於唯一標識一個節點。
  • 摒棄通過配置檔案配置 etcd 屬性的方式,你可以用環境變數的方式代替。
  • 通過自發現方式啟動叢集必須要提供叢集大小,這樣有助於使用者確定叢集實際啟動的節點數量。

3 etcd概念詞彙表

  • Raft:etcd所採用的保證分散式系統強一致性的演算法。
  • Node:一個Raft狀態機例項。
  • Member: 一個etcd例項。它管理著一個Node,並且可以為客戶端請求提供服務。
  • Cluster:由多個Member構成可以協同工作的etcd叢集。
  • Peer:對同一個etcd叢集中另外一個Member的稱呼。
  • Client: 向etcd叢集傳送HTTP請求的客戶端。
  • WAL:預寫式日誌,etcd用於持久化儲存的日誌格式。
  • snapshot:etcd防止WAL檔案過多而設定的快照,儲存etcd資料狀態。
  • Proxy:etcd的一種模式,為etcd叢集提供反向代理服務。
  • Leader:Raft演算法中通過競選而產生的處理所有資料提交的節點。
  • Follower:競選失敗的節點作為Raft中的從屬節點,為演算法提供強一致性保證。
  • Candidate:當Follower超過一定時間接收不到Leader的心跳時轉變為Candidate開始競選。
  • Term:某個節點成為Leader到下一次競選時間,稱為一個Term。
  • Index:資料項編號。Raft中通過Term和Index來定位資料。

4 叢集化應用實踐

etcd作為一個高可用鍵值儲存系統,天生就是為叢集化而設計的。由於Raft演算法在做決策時需要多數節點的投票,所以etcd一般部署叢集推薦奇數個節點,推薦的數量為3、5或者7個節點構成一個叢集。

4.1 叢集啟動

etcd有三種叢集化啟動的配置方案,分別為靜態配置啟動、etcd自身服務發現、通過DNS進行服務發現。

通過配置內容的不同,你可以對不同的方式進行選擇。值得一提的是,這也是新版etcd區別於舊版的一大特性,它摒棄了使用配置檔案進行引數配置的做法,轉而使用命令列引數或者環境變數的做法來配置引數。

4.1.1. 靜態配置

這種方式比較適用於離線環境,在啟動整個叢集之前,你就已經預先清楚所要配置的叢集大小,以及叢集上各節點的地址和埠資訊。那麼啟動時,你就可以通過配置 initial-cluster引數進行etcd叢集的啟動。

在每個etcd機器啟動時,配置環境變數或者新增啟動引數的方式如下。

12 ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"ETCD_INITIAL_CLUSTER_STATE=new

引數方法:

123 -initial-clusterinfra0=http://10.0.1.10:2380,http://10.0.1.11:2380,infra2=http://10.0.1.12:2380\-initial-cluster-statenew

值得注意的是-initial-cluster引數中配置的url地址必須與各個節點啟動時設定的 initial-advertise-peer-urls引數相同。( initial-advertise-peer-urls引數表示節點監聽其他節點同步訊號的地址)

如果你所在的網路環境配置了多個etcd叢集,為了避免意外發生,最好使用 -initial-cluster-token引數為每個叢集單獨配置一個token認證。這樣就可以確保每個叢集和叢集的成員都擁有獨特的ID。

綜上所述,如果你要配置包含3個etcd節點的叢集,那麼你在三個機器上的啟動命令分別如下所示。

1234567891011121314151617 $etcd-nameinfra0-initial-advertise-peer-urlshttp://10.0.1.10:2380\-listen-peer-urlshttp://10.0.1.10:2380\-initial-cluster-tokenetcd-cluster-1\-initial-clusterinfra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380\-initial-cluster-statenew$etcd-nameinfra1-initial-advertise-peer-urlshttp://10.0.1.11:2380\-listen-peer-urlshttp://10.0.1.11:2380\-initial-cluster-tokenetcd-cluster-1\-initial-clusterinfra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380\-initial-cluster-statenew$etcd-nameinfra2-initial-advertise-peer-urlshttp://10.0.1.12:2380\-listen-peer-urlshttp://10.0.1.12:2380\-initial-cluster-tokenetcd-cluster-1\-initial-clusterinfra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380\-initial-cluster-statenew

在初始化完成後,etcd還提供動態增、刪、改etcd叢集節點的功能,這個需要用到 etcdctl命令進行操作。

4.1.2. etcd自發現模式

通過自發現的方式啟動etcd叢集需要事先準備一個etcd叢集。如果你已經有一個etcd叢集,首先你可以執行如下命令設定叢集的大小,假設為3.

1 $curl-XPUThttp://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size-dvalue=3

然後你要把這個url地址 http://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83作為 -discovery引數來啟動etcd。節點會自動使用 http://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83目錄進行etcd的註冊和發現服務。

所以最終你在某個機器上啟動etcd的命令如下。

123 $etcd-nameinfra0-initial-advertise-peer-urlshttp://10.0.1.10:2380\-listen-peer-urlshttp://10.0.1.10:2380\-discoveryhttp://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

如果你本地沒有可用的etcd叢集,etcd官網提供了一個可以公網訪問的etcd儲存地址。你可以通過如下命令得到etcd服務的目錄,並把它作為 -discovery引數使用。

12 $curlhttp://discovery.etcd.io/new?size=3http://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

同樣的,當你完成了叢集的初始化後,這些資訊就失去了作用。當你需要增加節點時,需要使用 etcdctl來進行操作。

為了安全,請務必每次啟動新etcd叢集時,都使用新的discovery token進行註冊。另外,如果你初始化時啟動的節點超過了指定的數量,多餘的節點會自動轉化為Proxy模式的etcd。

4.1.3. DNS自發現模式

etcd還支援使用DNS SRV記錄進行啟動。關於DNS SRV記錄如何進行服務發現,可以參閱RFC2782,所以,你要在DNS伺服器上進行相應的配置。

(1) 開啟DNS伺服器上SRV記錄查詢,並新增相應的域名記錄,使得查詢到的結果類似如下。

1234 $dignoallanswerSRV_etcd-server._tcp.example.com_etcd-server._tcp.example.com.300INSRV002380infra0.example.com._etcd-server._tcp.example.com.300INSRV002380infra1.example.com._etcd-server._tcp.