可靠的分散式KV存儲產品-ETCD-初見

Paxos

官網://paxos.com/

Paxos演算法是萊斯利·蘭伯特於1990年提出的一種基於消息傳遞的一致性演算法

Raft(Understandable Distributed Consensus)

官網://raft.github.io/

論文://raft.github.io/raft.pdf

演算法演示網址://thesecretlivesofdata.com/raft/

Raft是一種共識演算法,旨在替代Paxos。

它通過邏輯分離比Paxos更容易理解,但它也被正式證明是安全的,並提供了一些額外的功能。

Raft提供了一種在計算系統集群中 分布狀態機 的通用方法,確保集群中的每個節點都同意一系列相同的狀態轉換。

問題分解

問題分解是將 「複製集中節點一致性” 這個複雜的問題劃分為數個可以被獨立解釋、理解、解決的子問題。

在raft,子問題包括,leader electionlog replicationsafetymembership changes

狀態簡化

對演算法做出一些限制,減少需要考慮的狀態數,使得演算法更加清晰。

raft會先選舉出leader,leader完全負責replicated log的管理。

leader負責接受所有客戶端更新請求,然後複製到follower節點,並在「安全」的時候執行這些請求。

名詞介紹

Raft將系統中的角色分為領導者(Leader)、跟從者(Follower)、候選人(Candidate)以及term( Raft演算法將時間分為一個個的任期)。

Raft要求系統在任意時刻最多只有一個Leader,正常工作期間只有Leader和Followers。

Raft演算法角色狀態轉換如下:

Leader Election

Raft 使用 heartbeat 觸發 Leader 選舉。

當伺服器啟動時,初始化為Follower。Leader向所有Followers周期性發送heartbeat。

如果Follower在選舉超時時間內沒有收到Leader的heartbeat,就會等待一段隨機的時間後發起一次Leader選舉。

Follower將其當前term加一然後轉換為Candidate。它首先給自己投票並且給集群中的其他伺服器發送RequestVote RPC 。

結果有以下三種情況:

  • 收到majority的投票(含自己的一票),則贏得選舉,成為leader。
贏得了選舉之後,新的leader會立刻給所有節點發消息,廣而告之,避免其餘節點觸發新的選舉。

投票者的視角,投票者如何決定是否給一個選舉請求投票呢,有以下約束:
- 在任一任期內,單個節點最多只能投一票。
- 候選人知道的資訊不能比自己的少(log replication和safety)。
- first-come-first-served 先來先得。
  • 被告知別人已當選,那麼自行切換到follower。

  • 一段時間內沒有收到majority投票,則保持candidate狀態,重新發出選舉。

平票 split vote 的情況。超時後重新發起選舉。

如果出現平票的情況,那麼就延長了系統不可用的時間(沒有leader是不能處理客戶端寫請求的),
因此raft引入了randomized election timeouts來盡量避免平票情況。

同時,leader-based 共識演算法中,節點的數目都是奇數個,盡量保證majority的出現。

Log Replication

有了leader,系統應該進入對外工作期了。

客戶端的一切請求來發送到leader,leader來調度這些並發請求的順序,並且保證leader與followers狀態的一致性。

將這些請求以及執行順序告知followers。leader和followers以相同的順序來執行這些請求,保證狀態一致。

為什麼是日誌?

如何保證所有節點 get the same inputs in the same order,使用replicated log是一個很不錯的注意。

log具有持久化、保序的特點,是大多數分散式系統的基石。

Replicated state machines

共識演算法的實現一般是基於複製狀態機(Replicated state machines),
何為複製狀態機:相同的初識狀態 + 相同的輸入 = 相同的結束狀態。

因此,可以這麼說,在raft中,leader將客戶端請求(command)封裝到一個個log entry,將這些log entries複製到所有follower節點,然後大家按相同順序應用log entry中的command,則狀態肯定是一致的。

下圖展示了這種log-based replicated state machine

請求完整流程

當系統(leader)收到一個來自客戶端的寫請求,到返回給客戶端,整個過程從leader的視角來看會經歷以下步驟:

  • leader append log entry
  • leader issue AppendEntries RPC in parallel
  • leader wait for majority response
  • leader apply entry to state machine
  • leader reply to client
  • leader notify follower apply log

leader只需要大多數(majority)節點的回復即可,這樣只要超過一半節點處於工作狀態則系統就是可用的。

logs由順序編號的log entry組成 ,每個log entry除了包含command,還包含產生該log entry時的leader term。

raft演算法為了保證高可用,並不是強一致性,而是最終一致性。

leader會不斷嘗試給follower發log entries,直到所有節點的log entries都相同。

在上面的流程中,leader只需要日誌被複制到大多數節點即可向客戶端返回,一旦向客戶端返回成功消息,那麼系統就必須保證log(其實是log所包含的command)在任何異常的情況下都不會發生回滾。

commit(committed)是指日誌被複制到了大多數節點後日誌的狀態
apply(applied) 是節點將日誌應用到狀態機,真正影響到節點狀態。

…………

etcd

文檔://etcd.io/docs/v3.5/

etcd 是 CoreOS 團隊發起的開源項目,目標是構建一個高可用的分散式鍵值(key-value)資料庫。

etcd 內部採用raft協議作為一致性演算法,etcd 基於 Go 語言實現。

總體架構

Blotddb:BoltDB是相當出名的純Go實現的KV讀寫引擎。

Wal: WAL(Write Ahead Log)預寫日誌,是資料庫系統中常見的一種手段,用於保證數據操作的原子性和持久性。

Snapshot:磁碟快照 (Snapshot)是針對整個磁碟卷冊進行快速的檔案系統備份。

在 etcd 架構中,有一個關鍵的概念叫做 quorum,quorum 的定義是 (n+1)/2,也就是說超過集群中半數節點組成的一個團體。

3個節點容忍1個故障,5個節點容忍2個故障。

允許部分節點故障之後繼續提供服務,怎麼解決分散式一致性?

分散式一致性演算法由 Raft 一致性演算法完成。

  • 任意兩個 quorum 的成員之間一定會有一個交集(公共成員),也就是說只要有任意一個 quorum 存活,其中一定存在某一個節點(公共成員),它包含著集群中所有的被確認提交的數據。

  • 基於這一原理,Raft 一致性演算法設計了一套數據同步機制,在 Leader 任期切換後能夠重新同步上一個quorum 被提交的所有數據,從而保證整個集群狀態向前推進的過程中保持數據的一致。

怎樣獲取數據呢?

  • 通過 etcd 提供的客戶端去訪問集群的數據。
  • 直接通過 http 的方式(類似 curl 命令)直接訪問 etcd。
  • 在 etcd 內部,把 etcd 的數據存儲理解為一個有序的 map,它存儲著 key-value 數據。
  • etcd 支援訂閱數據的變更,提供 watch 機制,通過 watch 實時地拿到 etcd 中數據的增量更新,從而實現與etcd 中的數據同步等業務邏輯。

API 介紹

API文檔: //github.com/coreos/etcd/blob/6acb3d67fbe131b3b2d5d010e00ec80182be4628/Documentation

配置項://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md

大致分為五組:

  • 第一組是 Put 與 Delete。類似Map的使用。
  • 第二組是查詢操作:
    • 第一種是指定單個 key 的查詢。
    • 第二種是指定的一個 key 的範圍。
  • 第三組是數據訂閱:
    • Watch 支援指定單個 key。
    • 指定一個 key 的前綴,在實際應用場景中的通常會採用第二種形勢;
  • 第四組事務操作。etcd 提供了一個簡單的事務支援,類似於程式碼中的 if else 語句,etcd 確保整個操作的原子性。
  • 第五組是 Leases 介面。
分散式系統理論之租約機制

在分散式系統中,往往會有一個中心伺服器節點。
該節點負責存儲、維護系統中的元數據。
如果系統中的各種操作都依賴於中心伺服器上的元數據,那麼中心伺服器很容易成為性能瓶頸及存在單點故障。
而通過租約機制,可以將中心伺服器的「權力」下放給其他機器,就可以減輕中心伺服器的壓力。

數據版本機制

要正確使用 etcd 的 API,必須要知道內部對應數據版本號的基本原理。

term:代表的是整個集群 Leader 的任期。

revision:代表的是全局數據的版本。

在集群中跨 Leader 任期之間,revision 都會保持全局單調遞增。
正是 revision 的這一特性,使得集群中任意一次的修改都對應著一個唯一的 revision,
因此我們可以通過 revision 來支援數據的 MVCC,也可以支援數據的 Watch。
MVVC (Multi-Version Concurrency Control) 

MVCC是為了實現事務的隔離性,通過版本號,避免同一數據在不同事務間的競爭,
可以把它當成基於多版本號的一種樂觀鎖。
當然,這種樂觀鎖只在事務級別提交讀和可重複讀有效。
MVCC最大的好處,相信也是耳熟能詳:讀不加鎖,讀寫不衝突。
在讀多寫少的OLTP應用中,讀寫不衝突是非常重要的,極大的增加了系統的並發性能。

對於每一個 KeyValue 數據節點,etcd 中都記錄了三個版本:

  • 第一個版本叫做 create_revision,是 KeyValue 在創建時對應的 revision。
  • 第二個叫做 mod_revision,是其數據被操作的時候對應的 revision。
  • 第三個 version 就是一個計數器,代表了 KeyValue 被修改了多少次。

數據訂閱

使用 etcd 多版本號來實現並發控制以及數據訂閱(Watch)。

  • 支援對同一個 Key 發起多次數據修改,每次數據修改都對應一個版本號。
  • 在查詢數據的時候如果不指定版本號,etcd 會返回 Key 對應的最新版本.
  • etcd 支援指定一個版本號來查詢歷史數據。

數據存儲

etcd 中所有的數據都存儲在一個 b+tree 中(灰色),該 b+tree 保存在磁碟中,並通過 mmap 的方式映射到記憶體用來支援快速的訪問。

灰色的 b+tree 中維護著 revision 到 value 的映射關係,支援通過 revision 查詢對應的數據。

revision 是單調遞增的,通過 watch 來訂閱指定 revision 之後的數據時,僅訂閱該 b+ tree 的數據變化即可。

在 etcd 內部還維護著另外一個 btree(藍色),它管理著 key 到 revision 的映射關係。當客戶端使用 key 查詢數據時,首先需要經過藍色的 btree 將 key 轉化為對應的 revision,再通過灰色的 btree 查詢到對應的數據。

問題

etcd 將每一次修改都記錄下來會導致數據持續增長。

這會帶來記憶體及磁碟的空間消耗,同時也會影響 b+tree 的查詢效率。

為了解決這一問題,在 etcd 中會運行一個周期性的 Compaction 的機制來清理歷史數據,將一段時間之前的同一個 Key 的多個歷史版本數據清理掉。

最終的結果是灰色的 b+tree 依舊保持單調遞增,但可能會出現一些空洞。

mini-transactions

etcd 的 事務機制比較簡單,基本可以理解為一段 if-else 程式。

在 etcd 內部會保證整個事務操作的原子性。

使用場景

  • 元數據存儲
  • Server Discovery (Naming Service)
  • Distributed Coordination: leader election
  • Distributed Coordination 分散式系統並發控制

參考

Paxos:

Raft:

etcd: