簡介: SOFAJRaft已開源java
做者 | 家純
來源 | 阿里技術公衆號node
1 如何理解分佈式共識?算法
多個參與者針對某一件事達成徹底一致:一件事,一個結論。編程
已達成一致的結論,不可推翻。緩存
2 有哪些分佈式共識算法?安全
Paxos:被認爲是分佈式共識算法的根本,其餘都是其變種,可是 paxos 論文中只給出了單個提案的過程,並無給出複製狀態機中須要的 multi-paxos 的相關細節的描述,實現 paxos 具備很高的工程複雜度(如多點可寫,容許日誌空洞等)。
Zab:被應用在 zookeeper 中,業界使用普遍,但沒用抽象成通用 library。
Raft:以容易理解著稱,業界也涌現出不少 raft 實現,好比 etcd、braft、tikv 等。性能優化
1 特色:Strong Leader服務器
系統中必須存在且同一時刻只能有一個 leader,只有 leader 能夠接受 clients 發過來的請求。
Leader 負責主動與全部 followers 通訊,負責將「提案」發送給全部followers,同時收集多數派的 followers 應答。
Leader 還需向全部 followers 主動發送心跳維持領導地位(保持存在感)。
另外,身爲 leader 必須保持一直 heartbeat 的狀態。網絡
2 複製狀態機併發
對於一個無限增加的序列a[1, 2, 3…],若是對於任意整數i, a[i]的值知足分佈式一致性, 這個系統就知足一致性狀態機的要求。
基本上全部的真實系統都會有源源不斷的操做,這時候單獨對某個特定的值達成一致顯然是不夠的。爲了讓真實系統保證全部的副本的一致性,一般會把操做轉化爲 write-ahead-log(WAL)。而後讓系統中全部副本對 WAL 保持一致,這樣每一個副本按照順序執行 WAL 裏的操做,就能保證最終的狀態是一致的。
Client 向 leader 發送寫請求。
Leader 把「操做」轉化爲 WAL 寫本地 log 的同時也將 log 複製到全部 followers。
Leader 收到多數派應答,將 log 對應的「操做」應用到狀態機。
回覆 client 處理結果。
3 Raft 中的基本概念
Raft-node 的 3 種角色/狀態
Message 的 3 種類型
任期邏輯時鐘
4 Raft 功能分解
Leader 選舉
超時驅動:Heartbeat / Election timeout
隨機的超時時間:下降選舉碰撞致使選票被瓜分的機率
選舉流程:Follower --> Candidate (選舉超時觸發)
選舉動做:
New Leader 選取原則 (最大提交原則)
安全性:一個 term,最多選出一個 leader,能夠沒 leader,下一個 term 再選。
影響 raft 選舉成功率的幾個時間參數
隨機選主觸發時間:Random(ET, 2ET)
日誌複製
Raft 日誌格式
Log replication關鍵點
不一樣節點,擁有相同 term 和 logIndex 的日誌 value 必定相同
Leader 上的日誌必定是有效的
Follower 上的日誌是否有效,經過 leader 日誌對比判斷 (How?)
Followers 日誌有效性檢查
Followers 日誌恢復
Commit Index 推動
CommitIndex (TermId, LogIndex)
CommitIndex推動
AppendEntries RPC
階段小結:如今咱們能用 raft 作什麼?
一個純 Java 的 raft 算法實現庫,使用 Java 重寫了全部功能,並有一些改進和優化。
1 SOFAJRaft 總體功能
功能支持
Leader election:選主。
Log replication and recovery:日誌複製和日誌恢復,log recovery就是要保證已經被 commit 的數據必定不會丟失,log recovery 包含兩個方面
Snapshot and log compaction:定時生成 snapshot,實現 log compaction加速啓動和恢復,以及InstallSnapshot 給 followers 拷貝數據。
Membership change:集羣線上配置變動,增長節點、刪除節點、替換節點等。
Transfer leader:主動變動 leader,用於重啓維護,leader 負載平衡等。
Symmetric network partition tolerance:對稱網絡分區容忍性。
Pre-Vote:如上圖 S1 爲當前 leader,網絡分區形成 S2 不斷增長本地 term,爲了不網絡恢復後S2發起選舉致使正在良心工做的 leader step-down, 從而致使整個集羣從新發起選舉,在 request-vote 以前會先進行 pre-vote(currentTerm + 1,lastLogIndex, lastLogTerm),多數派成功後纔會轉換狀態爲 candidate 發起真正的 request-vote,因此分區後的節點,pre-vote不會成功,也就不會致使集羣一段時間內沒法正常提供服務。
Asymmetric network partition tolerance:非對稱網絡分區容忍性。
如上圖 S1 爲當前 leader,S2 不斷超時觸發選主,S3 提高 term 打斷當前 lease,從而拒絕 leader 的更新,這個時候能夠增長一個 trick 的檢查,每一個 follower 維護一個時間戳記錄收到 leader 上數據更新的時間(也包括心跳),只有超過 election timeout 以後才容許接受 request-vote 請求。
Fault tolerance: 容錯性,少數派故障,不影響系統總體可用性:
Workaround when quorate peers are dead:多數派故障時整個 grop 已不具有可用性, 安全的作法是等待多數節點恢復,只有這樣才能保證數據安全,可是若是業務更追求可用性,放棄數據一致性的話能夠經過手動 reset_peers 指令迅速重建整個集羣,恢復集羣可用。
Metrics:SOFAJRaft 內置了基於 metrics 類庫的性能指標統計,具備豐富的性能統計指標。
Jepsen:除了單元測試以外,SOFAJRaft 還使用 jepsen 這個分佈式驗證和故障注入測試框架模擬了不少種狀況,都已驗證經過:
性能優化
Batch:SOFAJRaft 中整個鏈路都是 batch 的,依靠 disruptor 中的 MPSC 模型批量消費,包括但不限於:
Replication pipeline:流水線複製,leader 跟 followers 節點的 log 同步是串行 batch 的方式,每一個 batch 發送以後須要等待 batch 同步完成以後才能繼續發送下一批(ping-pong), 這樣會致使較長的延遲。能夠經過 leader 跟 followers 節點之間的 pipeline 複製來改進,有效下降更新的延遲, 提升吞吐。
Append log in parallel:Leader 持久化 log entries 和向 followers 發送 log entries 是並行的。
Fully concurrent replication:Leader 向全部 follwers 發送 log 也是徹底併發的。
Asynchronous:Jraft 中整個鏈路幾乎沒有任何阻塞,徹底異步的,是一個 callback 編程模型。
ReadIndex:優化 raft read 走 raft log 的性能問題,每次 read,僅記錄 commitIndex,而後發送全部 peers heartbeat 來確認 leader 身份,若是 leader 身份確認成功,等到 applied index >= commitIndex,就能夠返回 client read 了,基於 ReadIndex 能夠很方便的提供線性一致讀,不過 commitIndex 是須要從 leader 那裏獲取的,多了一輪RPC。
Lease Read:經過租約(lease)保證 leader 的身份,從而省去了 readIndex 每次 heartbeat 確認 leader 身份,性能更好, 可是經過時鐘維護 lease 自己並非絕對的安全(jraft 中默認配置是 readIndex,由於 readIndex 性能已足夠好)。
2 SOFAJRaft 設計
SOFAJRaft - Raft Node
Node:Raft 分組中的一個節點,鏈接封裝底層的全部服務,用戶看到的主要服務接口,特別是 apply(task) 用於向 raft group 組成的複製狀態機集羣提交新任務應用到業務狀態機。
存儲
狀態機
複製
RPC 模塊用於節點之間的網絡通信
KV Store:SOFAJRaft 只是一個 lib,KV Store 是 SOFAJRaft 的一個典型的應用場景,把它放進圖中以便更好的理解 SOFAJRaft。
SOFAJRaft - Raft Group
SOFAJRaft - Multi Raft Group
3 SOFAJRaft 實現細節
高效的線性一致讀
什麼是線性一致讀?
所謂線性一致讀,一個簡單的例子就是在 t1 的時刻咱們寫入了一個值, 那麼在 t1 以後, 咱們必定能讀到這個值,不可能讀到 t1 以前的舊值 (想一想 java 中的 volatile 關鍵字,說白了線性一致讀就是在分佈式系統中實現 volatile 語義)。
上圖Client A、B、C、D均符合線性一致讀,其中 D 看起來是 stale read,其實並非, D 請求橫跨了3個階段,而讀可能發生在任意時刻,因此讀到 1 或 2 都行。
重要:接下來的討論均基於一個大前提,就是業務狀態機的實現必須是知足線性一致性的, 簡單說就是也要具備 java volatile 的語義。
1)直接點,是否能夠直接從當前 leader 節點讀?
怎麼肯定當前的 leader 真的是 leader(網絡分區)?
2)最簡單的實現方式:讀請求走一遍 raft 協議
有什麼問題?
不只有日誌寫盤開銷,還有日誌複製的 RPC 開銷,在讀比重較大的系統中是沒法接受的
還多了一堆的 raft 「讀日誌」
3)ReadIndex Read
這是 raft 論文中提到過的一種優化方案,具體來講:
經過ReadIndex,也能夠很容易在 followers 節點上提供線性一致讀:
ReadIndex小結:
4)Lease Read
Lease read 與 ReadIndex 相似,但更進一步,不只省去了 log,還省去了網絡交互。它能夠大幅提高讀的吞吐也能顯著下降延時。
基本的思路是 leader 取一個比 election timeout 小的租期(最好小一個數量級),在租約期內不會發生選舉,這就確保了 leader 不會變,因此能夠跳過 ReadIndex 的第二步, 也就下降了延時。能夠看到, Lease read 的正確性和時間是掛鉤的,所以時間的實現相當重要,若是漂移嚴重,這套機制就會有問題。
實現方式:
5)更進一步:Wait Free
到此爲止 lease 省去了 ReadIndex 的第 2 步(heartbeat),實際上還能再進一步,省去第 3 步。
咱們想一想前面的實現方案的本質是什麼? 當前節點的狀態機達到「讀」這一刻的時間點 相同或者更新的狀態。
那麼更嚴格一點的約束就是:當前時刻,當前節點的狀態機就是最新的。
問題來了,leader 節點的狀態機能保證必定是最新的嗎?
小結:Wait Free 機制將最大程度的下降讀延遲,SOFAJRaft 暫未實現 wait free 這一優化,不過已經在計劃中。
在 SOFAJRaft 中發起一次線性一致讀請求:
// KV 存儲實現線性一致讀 public void readFromQuorum(String key, AsyncContext asyncContext) { // 請求 ID 做爲請求上下文傳入 byte[] reqContext = new byte[4]; Bits.putInt(reqContext, 0, requestId.incrementAndGet()); // 調用 readIndex 方法, 等待回調執行 this.node.readIndex(reqContext, new ReadIndexClosure() { @Override public void run(Status status, long index, byte[] reqCtx) { if (status.isOk()) { try { // ReadIndexClosure 回調成功, 能夠從狀態機讀取最新數據返回 // 若是你的狀態實現有版本概念, 能夠根據傳入的日誌 index 編號作讀取 asyncContext.sendResponse(new ValueCommand(fsm.getValue(key))); } catch (KeyNotFoundException e) { asyncContext.sendResponse(GetCommandProcessor.createKeyNotFoundResponse()); } } else { // 特定狀況下, 好比發生選舉, 該讀請求將失敗 asyncContext.sendResponse(new BooleanCommand(false, status.getErrorMsg())); } } }); }
1 SOFAJRaft 能夠作什麼
分佈式存儲系統,如分佈式消息隊列、分佈式文件系統、分佈式塊系統等等。
2 用戶案例
3 簡單實踐:基於 SOFAJRaft 設計一個簡單的 KV Store
到目前爲止,咱們彷佛還沒看到 SOFAJRaft 做爲一個 lib 有什麼特別之處, 由於 SOFAJRaft 能辦到的 zk,etcd 彷佛基本上也均可以辦到, 那麼 SOFAJRaft 算不算重複造輪子?
爲了說明 SOFAJRaft 具備很好的想象空間以及擴展能力,下面再介紹一個基於 SOFAJRaft 的複雜一些的實踐。
4 複雜一點的實踐:基於 SOFAJRaft 的 Rhea KV 的設計
功能名詞
特色
原文連接本文爲阿里雲原創內容,未經容許不得轉載。