ETCD 簡介 + 使用

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

經典應用場景

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

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

實際上,etcd做爲一個受到ZooKeeper與doozer啓發而催生的項目,除了擁有與之相似的功能外,更專一於如下四點。github

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

隨着雲計算的不斷髮展,分佈式系統中涉及到的問題愈來愈受到人們重視。受阿里中間件團隊對ZooKeeper典型應用場景一覽一文的啓發,筆者根據本身的理解也總結了一些etcd的經典使用場景。讓咱們來看看etcd這個基於Raft強一致性算法的分佈式存儲倉庫能給咱們帶來哪些幫助。算法

值得注意的是,分佈式系統中的數據分爲控制數據和應用數據。使用etcd的場景默認處理的數據都是控制數據,對於應用數據,只推薦數據量很小,可是更新訪問頻繁的狀況。docker

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

服務發現要解決的也是分佈式系統中最多見的問題之一,即在同一個分佈式集羣中的進程或服務,要如何才能找到對方並創建鏈接。本質上來講,服務發現就是想要了解集羣中是否有進程在監聽udp或tcp端口,而且經過名字就能夠查找和鏈接。要解決服務發現的問題,須要有下面三大支柱,缺一不可。apache

  1. 一個強一致性、高可用的服務存儲目錄。基於Raft算法的etcd天生就是這樣一個強一致性高可用的服務存儲目錄。
  2. 一種註冊服務和監控服務健康狀態的機制。用戶能夠在etcd中註冊服務,而且對註冊的服務設置key TTL,定時保持服務的心跳以達到監控健康狀態的效果。
  3. 一種查找和鏈接服務的機制。經過在etcd指定的主題下注冊的服務也能在對應的主題下查找到。爲了確保鏈接,咱們能夠在每一個服務機器上都部署一個Proxy模式的etcd,這樣就能夠確保能訪問etcd集羣的服務都能互相鏈接。

圖1 服務發現示意圖api

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

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

圖2 微服務協同工做服務器

  • PaaS平臺中應用多實例與實例故障重啓透明化。PaaS平臺中的應用通常都有多個實例,經過域名,不只能夠透明的對這多個實例進行訪問,並且還能夠作到負載均衡。可是應用的某個實例隨時都有可能故障重啓,這時就須要動態的配置域名解析(路由)中的信息。經過etcd的服務發現功能就能夠輕鬆解決這個動態配置的問題。

圖3 雲平臺多實例透明化

場景二:消息發佈與訂閱

在分佈式系統中,最適用的一種組件間通訊方式就是消息發佈與訂閱。即構建一個配置共享中心,數據提供者在這個配置中心發佈消息,而消息使用者則訂閱他們關心的主題,一旦主題有消息發佈,就會實時通知訂閱者。經過這種方式能夠作到分佈式系統配置的集中式管理與動態更新。

  • 應用中用到的一些配置信息放到etcd上進行集中管理。這類場景的使用方式一般是這樣:應用在啓動的時候主動從etcd獲取一次配置信息,同時,在etcd節點上註冊一個Watcher並等待,之後每次配置有更新的時候,etcd都會實時通知訂閱者,以此達到獲取最新配置信息的目的。
  • 分佈式搜索服務中,索引的元信息和服務器集羣機器的節點狀態存放在etcd中,供各個客戶端訂閱使用。使用etcd的key TTL功能能夠確保機器狀態是實時更新的。
  • 分佈式日誌收集系統。這個系統的核心工做是收集分佈在不一樣機器的日誌。收集器一般是按照應用(或主題)來分配收集任務單元,所以能夠在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. 節點能夠設置TTL key,好比每隔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通常部署集羣推薦奇數個節點,推薦的數量爲三、5或者7個節點構成一個集羣。

4.1 集羣啓動

etcd有三種集羣化啓動的配置方案,分別爲靜態配置啓動、etcd自身服務發現、經過DNS進行服務發現。

經過配置內容的不一樣,你能夠對不一樣的方式進行選擇。值得一提的是,這也是新版etcd區別於舊版的一大特性,它摒棄了使用配置文件進行參數配置的作法,轉而使用命令行參數或者環境變量的作法來配置參數。

4.1.1. 靜態配置

這種方式比較適用於離線環境,在啓動整個集羣以前,你就已經預先清楚所要配置的集羣大小,以及集羣上各節點的地址和端口信息。那麼啓動時,你就能夠經過配置initial-cluster參數進行etcd集羣的啓動。

在每一個etcd機器啓動時,配置環境變量或者添加啓動參數的方式以下。

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

參數方法:

-initial-cluster 
infra0=http://10.0.1.10:2380,http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
 -initial-cluster-state new

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

若是你所在的網絡環境配置了多個etcd集羣,爲了不意外發生,最好使用-initial-cluster-token參數爲每一個集羣單獨配置一個token認證。這樣就能夠確保每一個集羣和集羣的成員都擁有獨特的ID。

綜上所述,若是你要配置包含3個etcd節點的集羣,那麼你在三個機器上的啓動命令分別以下所示。

$ etcd -name infra0 -initial-advertise-peer-urls http://10.0.1.10:2380 \
  -listen-peer-urls http://10.0.1.10:2380 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  -initial-cluster-state new

$ etcd -name infra1 -initial-advertise-peer-urls http://10.0.1.11:2380 \
  -listen-peer-urls http://10.0.1.11:2380 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  -initial-cluster-state new

$ etcd -name infra2 -initial-advertise-peer-urls http://10.0.1.12:2380 \
  -listen-peer-urls http://10.0.1.12:2380 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  -initial-cluster-state new

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

4.1.2. etcd自發現模式

經過自發現的方式啓動etcd集羣須要事先準備一個etcd集羣。若是你已經有一個etcd集羣,首先你能夠執行以下命令設定集羣的大小,假設爲3.

$ curl -X PUT http://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

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

因此最終你在某個機器上啓動etcd的命令以下。

$ etcd -name infra0 -initial-advertise-peer-urls http://10.0.1.10:2380 \
  -listen-peer-urls http://10.0.1.10:2380 \
  -discovery http://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

若是你本地沒有可用的etcd集羣,etcd官網提供了一個能夠公網訪問的etcd存儲地址。你能夠經過以下命令獲得etcd服務的目錄,並把它做爲-discovery參數使用。

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

一樣的,當你完成了集羣的初始化後,這些信息就失去了做用。當你須要增長節點時,須要使用etcdctl來進行操做。

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

4.1.3. DNS自發現模式

etcd還支持使用DNS SRV記錄進行啓動。關於DNS SRV記錄如何進行服務發現,能夠參閱RFC2782,因此,你要在DNS服務器上進行相應的配置。

(1) 開啓DNS服務器上SRV記錄查詢,並添加相應的域名記錄,使得查詢到的結果相似以下。

$ dig +noall +answer SRV _etcd-server._tcp.example.com
_etcd-server._tcp.example.com. 300 IN   SRV 0 0 2380 infra0.example.com.
_etcd-server._tcp.example.com. 300 IN   SRV 0 0 2380 infra1.example.com.
_etcd-server._tcp.example.com. 300 IN   SRV 0 0 2380 infra2.example.com.

(2) 分別爲各個域名配置相關的A記錄指向etcd核心節點對應的機器IP。使得查詢結果相似以下。

$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com. 300 IN  A   10.0.1.10
infra1.example.com. 300 IN  A   10.0.1.11
infra2.example.com. 300 IN  A   10.0.1.12

作好了上述兩步DNS的配置,就可使用DNS啓動etcd集羣了。配置DNS解析的url參數爲-discovery-srv,其中某一個節點地啓動命令以下。

$ etcd -name infra0 \
-discovery-srv example.com \
-initial-advertise-peer-urls http://infra0.example.com:2380 \
-initial-cluster-token etcd-cluster-1 \
-initial-cluster-state new \
-advertise-client-urls http://infra0.example.com:2379 \
-listen-client-urls http://infra0.example.com:2379 \
-listen-peer-urls http://infra0.example.com:2380

固然,你也能夠直接把節點的域名改爲IP來啓動。

4.2 關鍵部分源碼解析

etcd的啓動是從主目錄下的main.go開始的,而後進入etcdmain/etcd.go,載入配置參數。若是被配置爲Proxy模式,則進入startProxy函數,不然進入startEtcd,開啓etcd服務模塊和http請求處理模塊。

在啓動http監聽時,爲了保持與集羣其餘etcd機器(peers)保持鏈接,都採用的transport.NewTimeoutListener啓動方式,這樣在超過指定時間沒有得到響應時就會出現超時錯誤。而在監聽client請求時,採用的是transport.NewKeepAliveListener,有助於鏈接的穩定。

etcdmain/etcd.go中的setupCluster函數能夠看到,根據不一樣etcd的參數,啓動集羣的方法略有不一樣,可是最終須要的就是一個IP與端口構成的字符串。

在靜態配置的啓動方式中,集羣的全部信息都已經在給出,因此直接解析用逗號隔開的集羣url信息就行了。

DNS發現的方式相似,會預先發送一個tcp的SRV請求,先查看etcd-server-ssl._tcp.example.com下是否有集羣的域名信息,若是沒有找到,則去查看etcd-server._tcp.example.com。根據找到的域名,解析出對應的IP和端口,即集羣的url信息。

較爲複雜是etcd式的自發現啓動。首先就用自身單個的url構成一個集羣,而後在啓動的過程當中根據參數進入discovery/discovery.go源碼的JoinCluster函數。由於咱們事先是知道啓動時使用的etcd的token地址的,裏面包含了集羣大小(size)信息。在這個過程實際上是個不斷監測與等待的過程。啓動的第一步就是在這個etcd的token目錄下注冊自身的信息,而後再監測token目錄下全部節點的數量,若是數量沒有達標,則循環等待。當數量達到要求時,才結束,進入正常的啓動過程。

配置etcd過程當中一般要用到兩種url地址容易混淆,一種用於etcd集羣同步信息並保持鏈接,一般稱爲peer-urls;另一種用於接收用戶端發來的HTTP請求,一般稱爲client-urls。

  • peer-urls:一般監聽的端口爲2380(老版本使用的端口爲7001),包括全部已經在集羣中正常工做的全部節點的地址。
  • client-urls:一般監聽的端口爲2379(老版本使用的端口爲4001),爲適應複雜的網絡環境,新版etcd監聽客戶端請求的url從原來的1個變爲如今可配置的多個。這樣etcd能夠配合多塊網卡同時監聽不一樣網絡下的請求。

4.3 運行時節點變動

etcd集羣啓動完畢後,能夠在運行的過程當中對集羣進行重構,包括核心節點的增長、刪除、遷移、替換等。運行時重構使得etcd集羣無須重啓便可改變集羣的配置,這也是新版etcd區別於舊版包含的新特性。

只有當集羣中多數節點正常的狀況下,你才能夠進行運行時的配置管理。由於配置更改的信息也會被etcd當成一個信息存儲和同步,若是集羣多數節點損壞,集羣就失去了寫入數據的能力。因此在配置etcd集羣數量時,強烈推薦至少配置3個核心節點。

4.3.1. 節點遷移、替換

當你節點所在的機器出現硬件故障,或者節點出現如數據目錄損壞等問題,致使節點永久性的不可恢復時,就須要對節點進行遷移或者替換。當一個節點失效之後,必須儘快修復,由於etcd集羣正常運行的必要條件是集羣中多數節點都正常工做。

遷移一個節點須要進行四步操做:

  • 暫停正在運行着的節點程序進程
  • 把數據目錄從現有機器拷貝到新機器
  • 使用api更新etcd中對應節點指向機器的url記錄更新爲新機器的ip
  • 使用一樣的配置項和數據目錄,在新的機器上啓動etcd。

4.3.2. 節點增長

增長節點可讓etcd的高可用性更強。舉例來講,若是你有3個節點,那麼最多容許1個節點失效;當你有5個節點時,就能夠容許有2個節點失效。同時,增長節點還可讓etcd集羣具備更好的讀性能。由於etcd的節點都是實時同步的,每一個節點上都存儲了全部的信息,因此增長節點能夠從總體上提高讀的吞吐量。

增長一個節點須要進行兩步操做:

  • 在集羣中添加這個節點的url記錄,同時得到集羣的信息。
  • 使用得到的集羣信息啓動新etcd節點。

4.3.3. 節點移除

有時你不得不在提升etcd的寫性能和增長集羣高可用性上進行權衡。Leader節點在提交一個寫記錄時,會把這個消息同步到每一個節點上,當獲得多數節點的贊成反饋後,纔會真正寫入數據。因此節點越多,寫入性能越差。在節點過多時,你可能須要移除一個或多個。

移除節點很是簡單,只須要一步操做,就是把集羣中這個節點的記錄刪除。而後對應機器上的該節點就會自動中止。

4.3.4. 強制性重啓集羣

當集羣超過半數的節點都失效時,就須要經過手動的方式,強制性讓某個節點以本身爲Leader,利用原有數據啓動一個新集羣。

此時你須要進行兩步操做。

  • 備份原有數據到新機器。
  • 使用-force-new-cluster加備份的數據從新啓動節點

注意:強制性重啓是一個無可奈何的選擇,它會破壞一致性協議保證的安全性(若是操做時集羣中尚有其它節點在正常工做,就會出錯),因此在操做前請務必要保存好數據。

5 Proxy模式

Proxy模式也是新版etcd的一個重要變動,etcd做爲一個反向代理把客戶的請求轉發給可用的etcd集羣。這樣,你就能夠在每一臺機器都部署一個Proxy模式的etcd做爲本地服務,若是這些etcd Proxy都能正常運行,那麼你的服務發現必然是穩定可靠的。

圖11 Proxy模式示意圖

因此Proxy並非直接加入到符合強一致性的etcd集羣中,也一樣的,Proxy並無增長集羣的可靠性,固然也沒有下降集羣的寫入性能。

5.1 Proxy取代Standby模式的緣由

那麼,爲何要有Proxy模式而不是直接增長etcd核心節點呢?實際上etcd每增長一個核心節點(peer),都會增長Leader節點必定程度的包括網絡、CPU和磁盤的負擔,由於每次信息的變化都須要進行同步備份。增長etcd的核心節點可讓整個集羣具備更高的可靠性,可是當數量達到必定程度之後,增長可靠性帶來的好處就變得不那麼明顯,反卻是下降了集羣寫入同步的性能。所以,增長一個輕量級的Proxy模式etcd節點是對直接增長etcd核心節點的一個有效代替。

熟悉0.4.6這個舊版本etcd的用戶會發現,Proxy模式其實是取代了原先的Standby模式。Standby模式除了轉發代理的功能之外,還會在覈心節點由於故障致使數量不足的時候,從Standby模式轉爲正常節點模式。而當那個故障的節點恢復時,發現etcd的核心節點數量已經達到的預先設置的值,就會轉爲Standby模式。

可是新版etcd中,只會在最初啓動etcd集羣時,發現核心節點的數量已經知足要求時,自動啓用Proxy模式,反之則並未實現。主要緣由以下。

  • etcd是用來保證高可用的組件,所以它所須要的系統資源(包括內存、硬盤和CPU等)都應該獲得充分保障以保證高可用。任由集羣的自動變換隨意地改變核心節點,沒法讓機器保證性能。因此etcd官方鼓勵你們在大型集羣中爲運行etcd準備專有機器集羣。
  • 由於etcd集羣是支持高可用的,部分機器故障並不會致使功能失效。因此機器發生故障時,管理員有充分的時間對機器進行檢查和修復。
  • 自動轉換使得etcd集羣變得複雜,尤爲是現在etcd支持多種網絡環境的監聽和交互。在不一樣網絡間進行轉換,更容易發生錯誤,致使集羣不穩定。

基於上述緣由,目前Proxy模式有轉發代理功能,而不會進行角色轉換。

5.2 關鍵部分源碼解析

從代碼中能夠看到,Proxy模式的本質就是起一個HTTP代理服務器,把客戶發到這個服務器的請求轉發給別的etcd節點。

etcd目前支持讀寫皆可和只讀兩種模式。默認狀況下是讀寫皆可,就是把讀、寫兩種請求都進行轉發。而只讀模式只轉發讀的請求,對全部其餘請求返回501錯誤。

值得注意的是,除了啓動過程當中由於設置了proxy參數會做爲Proxy模式啓動。在etcd集羣化啓動時,節點註冊自身的時候監測到集羣的實際節點數量已經符合要求,那麼就會退化爲Proxy模式。

6 數據存儲

etcd的存儲分爲內存存儲和持久化(硬盤)存儲兩部分,內存中的存儲除了順序化的記錄下全部用戶對節點數據變動的記錄外,還會對用戶數據進行索引、建堆等方便查詢的操做。而持久化則使用預寫式日誌(WAL:Write Ahead Log)進行記錄存儲。

在WAL的體系中,全部的數據在提交以前都會進行日誌記錄。在etcd的持久化存儲目錄中,有兩個子目錄。一個是WAL,存儲着全部事務的變化記錄;另外一個則是snapshot,用於存儲某一個時刻etcd全部目錄的數據。經過WAL和snapshot相結合的方式,etcd能夠有效的進行數據存儲和節點故障恢復等操做。

既然有了WAL實時存儲了全部的變動,爲何還須要snapshot呢?隨着使用量的增長,WAL存儲的數據會暴增,爲了防止磁盤很快就爆滿,etcd默認每10000條記錄作一次snapshot,通過snapshot之後的WAL文件就能夠刪除。而經過API能夠查詢的歷史etcd操做默認爲1000條。

首次啓動時,etcd會把啓動的配置信息存儲到data-dir參數指定的數據目錄中。配置信息包括本地節點的ID、集羣ID和初始時集羣信息。用戶須要避免etcd從一個過時的數據目錄中從新啓動,由於使用過時的數據目錄啓動的節點會與集羣中的其餘節點產生不一致(如:以前已經記錄並贊成Leader節點存儲某個信息,重啓後又向Leader節點申請這個信息)。因此,爲了最大化集羣的安全性,一旦有任何數據損壞或丟失的可能性,你就應該把這個節點從集羣中移除,而後加入一個不帶數據目錄的新節點。

6.1 預寫式日誌(WAL)

WAL(Write Ahead Log)最大的做用是記錄了整個數據變化的所有歷程。在etcd中,全部數據的修改在提交前,都要先寫入到WAL中。使用WAL進行數據的存儲使得etcd擁有兩個重要功能。

  • 故障快速恢復: 當你的數據遭到破壞時,就能夠經過執行全部WAL中記錄的修改操做,快速從最原始的數據恢復到數據損壞前的狀態。
  • 數據回滾(undo)/重作(redo):由於全部的修改操做都被記錄在WAL中,須要回滾或重作,只須要方向或正向執行日誌中的操做便可。

WAL與snapshot在etcd中的命名規則

在etcd的數據目錄中,WAL文件以$seq-$index.wal的格式存儲。最初始的WAL文件是0000000000000000-0000000000000000.wal,表示是全部WAL文件中的第0個,初始的Raft狀態編號爲0。運行一段時間後可能須要進行日誌切分,把新的條目放到一個新的WAL文件中。

假設,當集羣運行到Raft狀態爲20時,須要進行WAL文件的切分時,下一份WAL文件就會變爲0000000000000001-0000000000000021.wal。若是在10次操做後又進行了一第二天志切分,那麼後一次的WAL文件名會變爲0000000000000002-0000000000000031.wal。能夠看到-符號前面的數字是每次切分後自增1,而-符號後面的數字則是根據實際存儲的Raft起始狀態來定。

snapshot的存儲命名則比較容易理解,以$term-$index.wal格式進行命名存儲。term和index就表示存儲snapshot時數據所在的raft節點狀態,當前的任期編號以及數據項位置信息。

6.2 關鍵部分源碼解析

從代碼邏輯中能夠看到,WAL有兩種模式,讀模式(read)和數據添加(append)模式,兩種模式不能同時成立。一個新建立的WAL文件處於append模式,而且不會進入到read模式。一個原本存在的WAL文件被打開的時候必然是read模式,而且只有在全部記錄都被讀完的時候,才能進入append模式,進入append模式後也不會再進入read模式。這樣作有助於保證數據的完整與準確。

集羣在進入到etcdserver/server.goNewServer函數準備啓動一個etcd節點時,會檢測是否存在之前的遺留WAL數據。

檢測的第一步是查看snapshot文件夾下是否有符合規範的文件,若檢測到snapshot格式是v0.4的,則調用函數升級到v0.5。從snapshot中得到集羣的配置信息,包括token、其餘節點的信息等等,而後載入WAL目錄的內容,從小到大進行排序。根據snapshot中獲得的term和index,找到WAL緊接着snapshot下一條的記錄,而後向後更新,直到全部WAL包的entry都已經遍歷完畢,Entry記錄到ents變量中存儲在內存裏。此時WAL就進入append模式,爲數據項添加進行準備。

當WAL文件中數據項內容過大達到設定值(默認爲10000)時,會進行WAL的切分,同時進行snapshot操做。這個過程能夠在etcdserver/server.gosnapshot函數中看到。因此,實際上數據目錄中有用的snapshot和WAL文件各只有一個,默認狀況下etcd會各保留5個歷史文件。

7 Raft

新版etcd中,raft包就是對Raft一致性算法的具體實現。關於Raft算法的講解,網上已經有不少文章,有興趣的讀者能夠去閱讀一下Raft算法論文很是精彩。本文則再也不對Raft算法進行詳細描述,而是結合etcd,針對算法中一些關鍵內容以問答的形式進行講解。有關Raft算法的術語若是不理解,能夠參見概念詞彙表一節。

7.1 Raft常見問答一覽

  • Raft中一個Term(任期)是什麼意思? Raft算法中,從時間上,一個任期講即從一次競選開始到下一次競選開始。從功能上講,若是Follower接收不到Leader節點的心跳信息,就會結束當前任期,變爲Candidate發起競選,有助於Leader節點故障時集羣的恢復。發起競選投票時,任期值小的節點不會競選成功。若是集羣不出現故障,那麼一個任期將無限延續下去。而投票出現衝突也有可能直接進入下一任再次競選。

    圖12 Term示意圖

  • Raft狀態機是怎樣切換的? Raft剛開始運行時,節點默認進入Follower狀態,等待Leader發來心跳信息。若等待超時,則狀態由Follower切換到Candidate進入下一輪term發起競選,等到收到集羣多數節點的投票時,該節點轉變爲Leader。Leader節點有可能出現網絡等故障,致使別的節點發起投票成爲新term的Leader,此時原先的老Leader節點會切換爲Follower。Candidate在等待其它節點投票的過程當中若是發現別的節點已經競選成功成爲Leader了,也會切換爲Follower節點。

    圖13 Raft狀態機

  • 如何保證最短期內競選出Leader,防止競選衝突? 在Raft狀態機一圖中能夠看到,在Candidate狀態下, 有一個times out,這裏的times out時間是個隨機值,也就是說,每一個機器成爲Candidate之後,超時發起新一輪競選的時間是各不相同的,這就會出現一個時間差。在時間差內,若是Candidate1收到的競選信息比本身發起的競選信息term值大(即對方爲新一輪term),而且新一輪想要成爲Leader的Candidate2包含了全部提交的數據,那麼Candidate1就會投票給Candidate2。這樣就保證了只有很小的機率會出現競選衝突。
  • 如何防止別的Candidate在遺漏部分數據的狀況下發起投票成爲Leader? Raft競選的機制中,使用隨機值決定超時時間,第一個超時的節點就會提高term編號發起新一輪投票,通常狀況下別的節點收到競選通知就會投票。可是,若是發起競選的節點在上一個term中保存的已提交數據不完整,節點就會拒絕投票給它。經過這種機制就能夠防止遺漏數據的節點成爲Leader。
  • Raft某個節點宕機後會如何? 一般狀況下,若是是Follower節點宕機,若是剩餘可用節點數量超過半數,集羣能夠幾乎沒有影響的正常工做。若是是Leader節點宕機,那麼Follower就收不到心跳而超時,發起競選得到投票,成爲新一輪term的Leader,繼續爲集羣提供服務。須要注意的是;etcd目前沒有任何機制會自動去變化整個集羣總共的節點數量,即若是沒有人爲的調用API,etcd宕機後的節點仍然被計算爲總節點數中,任何請求被確認須要得到的投票數都是這個總數的半數以上。

    圖14 節點宕機

  • 爲何Raft算法在肯定可用節點數量時不須要考慮拜占庭將軍問題? 拜占庭問題中提出,容許n個節點宕機還能提供正常服務的分佈式架構,須要的總節點數量爲3n+1,而Raft只須要2n+1就能夠了。其主要緣由在於,拜占庭將軍問題中存在數據欺騙的現象,而etcd中假設全部的節點都是誠實的。etcd在競選前須要告訴別的節點自身的term編號以及前一輪term最終結束時的index值,這些數據都是準確的,其餘節點能夠根據這些值決定是否投票。另外,etcd嚴格限制Leader到Follower這樣的數據流向保證數據一致不會出錯。
  • 用戶從集羣中哪一個節點讀寫數據? Raft爲了保證數據的強一致性,全部的數據流向都是一個方向,從Leader流向Follower,也就是全部Follower的數據必須與Leader保持一致,若是不一致會被覆蓋。即全部用戶更新數據的請求都最早由Leader得到,而後存下來通知其餘節點也存下來,等到大多數節點反饋時再把數據提交。一個已提交的數據項纔是Raft真正穩定存儲下來的數據項,再也不被修改,最後再把提交的數據同步給其餘Follower。由於每一個節點都有Raft已提交數據準確的備份(最壞的狀況也只是已提交數據還未徹底同步),因此讀的請求任意一個節點均可以處理。
  • etcd實現的Raft算法性能如何? 單實例節點支持每秒1000次數據寫入。節點越多,因爲數據同步涉及到網絡延遲,會根據實際狀況愈來愈慢,而讀性能會隨之變強,由於每一個節點都能處理用戶請求。

7.2 關鍵部分源碼解析

在etcd代碼中,Node做爲Raft狀態機的具體實現,是整個算法的關鍵,也是瞭解算法的入口。

在etcd中,對Raft算法的調用以下,你能夠在etcdserver/raft.go中的startNode找到:

storage := raft.NewMemoryStorage()
n := raft.StartNode(0x01, []int64{0x02, 0x03}, 3, 1, storage)

經過這段代碼能夠了解到,Raft在運行過程記錄數據和狀態都是保存在內存中,而代碼中raft.StartNode啓動的Node就是Raft狀態機Node。啓動了一個Node節點後,Raft會作以下事項。

首先,你須要把從集羣的其餘機器上收到的信息推送到Node節點,你能夠在etcdserver/server.go中的Process函數看到。

func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error {
    if m.Type == raftpb.MsgApp {
        s.stats.RecvAppendReq(types.ID(m.From).String(), m.Size())
    }
    return s.node.Step(ctx, m)
}

在檢測發來請求的機器是不是集羣中的節點,自身節點是不是Follower,把發來請求的機器做爲Leader,具體對Node節點信息的推送和處理則經過node.Step()函數實現。

其次,你須要把日誌項存儲起來,在你的應用中執行提交的日誌項,而後把完成信號發送給集羣中的其它節點,再經過node.Ready()監聽等待下一次任務執行。有一點很是重要,你必須確保在你發送完成消息給其餘節點以前,你的日誌項內容已經確切穩定的存儲下來了。

最後,你須要保持一個心跳信號Tick()。Raft有兩個很重要的地方用到超時機制:心跳保持和Leader競選。須要用戶在其raft的Node節點上週期性的調用Tick()函數,以便爲超時機制服務。

綜上所述,整個raft節點的狀態機循環相似以下所示:

for {
    select {
    case <-s.Ticker:
        n.Tick()
    case rd := <-s.Node.Ready():
        saveToStorage(rd.State, rd.Entries)
        send(rd.Messages)
        process(rd.CommittedEntries)
        s.Node.Advance()
    case <-s.done:
        return
    }
}

而這個狀態機真實存在的代碼位置爲etcdserver/server.go中的run函數。

對狀態機進行狀態變動(如用戶數據更新等)則是調用n.Propose(ctx, data)函數,在存儲數據時,會先進行序列化操做。得到大多數其餘節點的確認後,數據會被提交,存爲已提交狀態。

以前提到etcd集羣的啓動須要藉助別的etcd集羣或者DNS,而啓動完畢後這些外力就不須要了,etcd會把自身集羣的信息做爲狀態存儲起來。因此要變動自身集羣節點數量實際上也須要像用戶數據變動那樣添加數據條目到Raft狀態機中。這一切由n.ProposeConfChange(ctx, cc)實現。當集羣配置信息變動的請求一樣獲得大多數節點的確認反饋後,再進行配置變動的正式操做,代碼以下。

var cc raftpb.ConfChange
cc.Unmarshal(data)
n.ApplyConfChange(cc)

注意:一個ID惟一性的表示了一個集羣,因此爲了不不一樣etcd集羣消息混亂,ID須要確保惟一性,不能重複使用舊的token數據做爲ID。

8 Store

Store這個模塊顧名思義,就像一個商店把etcd已經準備好的各項底層支持加工起來,爲用戶提供五花八門的API支持,處理用戶的各項請求。要理解Store,只須要從etcd的API入手便可。打開etcd的API列表,咱們能夠看到有以下API是對etcd存儲的鍵值進行的操做,亦即Store提供的內容。API中提到的目錄(Directory)和鍵(Key),上文中也可能稱爲etcd節點(Node)。

  • 爲etcd存儲的鍵賦值
    curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="Hello world"
    {
        "action": "set",
        "node": {
            "createdIndex": 2,
            "key": "/message",
            "modifiedIndex": 2,
            "value": "Hello world"
        }
    }

反饋的內容含義以下:

  • action: 剛剛進行的動做名稱。
  • node.key: 請求的HTTP路徑。etcd使用一個相似文件系統的方式來反映鍵值存儲的內容。
  • node.value: 剛剛請求的鍵所存儲的內容。
  • node.createdIndex: etcd節點每次有變化時都會自增的一個值,除了用戶請求外,etcd內部運行(如啓動、集羣信息變化等)也會對節點有變更而引發這個值的變化。
  • node.modifiedIndex: 相似node.createdIndex,能引發modifiedIndex變化的操做包括set, delete, update, create, compareAndSwap and compareAndDelete。
  • 查詢etcd某個鍵存儲的值
    curl http://127.0.0.1:2379/v2/keys/message
  • 修改鍵值:與建立新值幾乎相同,可是反饋時會有一個prevNode值反應了修改前存儲的內容。
    curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="Hello etcd"
  • 刪除一個值
    curl http://127.0.0.1:2379/v2/keys/message -XDELETE
  • 對一個鍵進行定時刪除:etcd中對鍵進行定時刪除,設定一個TTL值,當這個值到期時鍵就會被刪除。反饋的內容會給出expiration項告知超時時間,ttl項告知設定的時長。
    curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -d ttl=5
  • 取消定時刪除任務
    curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -d ttl= -d prevExist=true
  • 對鍵值修改進行監控:etcd提供的這個API讓用戶能夠監控一個值或者遞歸式的監控一個目錄及其子目錄的值,當目錄或值發生變化時,etcd會主動通知。
    curl http://127.0.0.1:2379/v2/keys/foo?wait=true
  • 對過去的鍵值操做進行查詢:相似上面提到的監控,只不過監控時加上了過去某次修改的索引編號,就能夠查詢歷史操做。默承認查詢的歷史記錄爲1000條。
    curl 'http://127.0.0.1:2379/v2/keys/foo?wait=true&waitIndex=7'
  • 自動在目錄下建立有序鍵。在對建立的目錄使用POST參數,會自動在該目錄下建立一個以createdIndex值爲鍵的值,這樣就至關於以建立時間前後嚴格排序了。這個API對分佈式隊列這類場景很是有用。
    curl http://127.0.0.1:2379/v2/keys/queue -XPOST -d value=Job1
    {
        "action": "create",
        "node": {
            "createdIndex": 6,
            "key": "/queue/6",
            "modifiedIndex": 6,
            "value": "Job1"
        }
    }
  • 按順序列出全部建立的有序鍵。
    curl -s 'http://127.0.0.1:2379/v2/keys/queue?recursive=true&sorted=true'
  • 建立定時刪除的目錄:就跟定時刪除某個鍵相似。若是目錄由於超時被刪除了,其下的全部內容也自動超時刪除。
    curl http://127.0.0.1:2379/v2/keys/dir -XPUT -d ttl=30 -d dir=true

刷新超時時間。

    curl http://127.0.0.1:2379/v2/keys/dir -XPUT -d ttl=30 -d dir=true -d prevExist=true
  • 自動化CAS(Compare-and-Swap)操做:etcd強一致性最直觀的表現就是這個API,經過設定條件,阻止節點二次建立或修改。即用戶的指令被執行當且僅當CAS的條件成立。條件有如下幾個。
    • prevValue 先前節點的值,若是值與提供的值相同才容許操做。
    • prevIndex 先前節點的編號,編號與提供的校驗編號相同才容許操做。
    • prevExist 先前節點是否存在。若是存在則不容許操做。這個經常被用於分佈式鎖的惟一獲取。

假設先進行了以下操做:設定了foo的值。

curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=one

而後再進行操做:

curl http://127.0.0.1:2379/v2/keys/foo?prevExist=false -XPUT -d value=three

就會返回建立失敗的錯誤。

  • 條件刪除(Compare-and-Delete):與CAS相似,條件成立後才能刪除。
  • 建立目錄
    curl http://127.0.0.1:2379/v2/keys/dir -XPUT -d dir=true
  • 列出目錄下全部的節點信息,最後以/結尾。還能夠經過recursive參數遞歸列出全部子目錄信息。
    curl http://127.0.0.1:2379/v2/keys/
  • 刪除目錄:默認狀況下只容許刪除空目錄,若是要刪除有內容的目錄須要加上recursive=true參數。
    curl 'http://127.0.0.1:2379/v2/keys/foo_dir?dir=true' -XDELETE
  • 建立一個隱藏節點:命名時名字如下劃線_開頭默認就是隱藏鍵。
    curl http://127.0.0.1:2379/v2/keys/_message -XPUT -d value="Hello hidden world"

相信看完這麼多API,讀者已經對Store的工做內容基本瞭解了。它對etcd下存儲的數據進行加工,建立出如文件系統般的樹狀結構供用戶快速查詢。它有一個Watcher用於節點變動的實時反饋,還須要維護一個WatcherHub對全部Watcher訂閱者進行通知的推送。同時,它還維護了一個由定時鍵構成的小頂堆,快速返回下一個要超時的鍵。最後,全部這些API的請求都以事件的形式存儲在事件隊列中等待處理。

9 總結

經過從應用場景到源碼分析的一系列回顧,咱們瞭解到etcd並非一個簡單的分佈式鍵值存儲系統。它解決了分佈式場景中最爲常見的一致性問題,爲服務發現提供了一個穩定高可用的消息註冊倉庫,爲以微服務協同工做的架構提供了無限的可能。相信在不久的未來,經過etcd構建起來的大型系統會愈來愈多。

參考文獻

  1. https://github.com/coreos/etcd
  2. https://groups.google.com/forum/#!topic/etcd-dev/wmndjzBNdZo
  3. http://jm-blog.aliapp.com/?p=1232
  4. http://progrium.com/blog/2014/07/29/understanding-modern-service-discovery-with-docker/
  5. http://devo.ps/blog/zookeeper-vs-doozer-vs-etcd/
  6. http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/
  7. http://www.infoworld.com/article/2612082/open-source-software/has-apache-lost-its-way-.html
  8. http://en.wikipedia.org/wiki/WAL
  9. http://www.infoq.com/cn/articles/coreos-analyse-etcd
  10. http://www.activestate.com/blog/2014/05/service-discovery-solutions
  11. https://ramcloud.stanford.edu/raft.pdf
相關文章
相關標籤/搜索