Etcd提供了一個樣例contrib/raftexample,用來展示如何使用etcd raft。這篇文章通過raftexample介紹如何使用etcd raft。
raftexample是一個分布式KV資料庫,客戶端可以向集群的節點發送寫數據和讀數據,以及修改集群配置的請求,它使用etcd raft保持各集群之間數據的一致性。
etcd raft實現了raft論文的核心,所有的IO(磁碟存儲、網絡通信)它都沒有實現,它做了解耦。
它是一個狀態機,有數據作為輸入,經過當前狀態和輸入,得到確定性的輸出,即每個節點上都是一樣的。
raft集群會由多個節點組成,客戶端的請求發送給raft leader,再由raft leader通過網絡通信在集群之中對請求達成共識。
集群中的每個節點從架構上都可以分為兩層:
共識層由etcd raft負責,應用層要負責業務邏輯,數據存儲和網絡通信不需要應用層實現,而是由不同的模塊負責,應用層負責起銜接存儲存儲和網絡通信即可。
應用層有3個重要組成部分:http API、kv store和raftNode。
每個節點都會啟動一個http API用來接受客戶端請求,它只是接收請求,不對請求做處理。它會把客戶端的寫入請求PUT和查詢請求GET都交給kv store。
對於修改raft集群配置請求,它會生成ConfChange交給raftNode。
一個kv資料庫服務,它保存有一個kv db,用來存儲用戶數據。
raftNode用來跟etcd raft交互,他需要:
對於寫請求,它會把請求數據編碼後發送給etcd raft,etcd raft會把寫請求封裝成raft的Propose消息MsgProp,編碼後的數據成為log Entry。因為raft並不關心具體的請求內容,它只需要保證每個集群節點在相同的log index擁有相同的log Entry,即請求即可。
raftNode還會啟動1個http server,用來集群節點之間的通信,傳遞raft消息,讓集群節點達成共識。它與http api是不同的,http api用來接收用戶請求。
raft模塊內部定義了一個Node接口,它代表了raft集群中的一個raft節點,它是應用層跟共識層交互的接口。
其中有幾個與數據傳遞相關函數的是:
還有一個ApplyConfChange函數,當Ready結構體中包含修改raft集群配置的log entry時,應用層會調用此函數,把配置應用到raft。
瞄完raft應用架構,可以從宏觀角度看一下raft是如何跟應用層對接的。
raft包內部有2個很重要的結構體:node和raft。
node結構體(後續稱為raft.node)實現了Node接口,負責跟應用層對接,raft.node有個goroutine持續運行,應用層raftNode也有goroutine持續運行,raftNode調用raft.node的函數,每個函數都有對應的一個channel,用來把raftNode要傳遞給raft的數據,發送給raft.node。比如Propose函數的通道是proc,Step函數的通道是recvc。
raft結構體(後續稱為raft.raft)是raft算法的主要實現。
raft.node把輸入推給raft.raft,raft.raft根據輸入和當前的狀態數據生成輸出,輸出臨時保存在raft內,raft.node會檢查raft.raft是否有輸出,如果有輸出數據,就把輸出生成Ready結構體,並傳遞給應用層。
raft.raft應用層有一個storage,存放的是當前的狀態數據,包含了保存在內存中的log entry,但這個storage並不是raft.raft的,是應用層的,raft.raft只從中讀取數據,log entry的寫入由應用層負責。
WAL是Write Ahead Logs的縮寫,存儲的是log entry記錄,即所有寫請求的記錄。
storage也是存的log entry,只不過是保存在內存中的。
kv db是保存了所有數據的最新值,而log entry是修改數據值的操作記錄。
log entry在集群節點之間達成共識之後,log entry會寫入WAL文件,也會寫入storage,然後會被應用到kv store中,改變kv db中的數據。
Snapshot是kv db是某個log entry被應用後生成的快照,可以根據快照快速回復kv db,而無需從所有的歷史log entry依次應用,恢復kv db。
有了上面架構層面的了解,我們從宏觀的角度看一下一個寫請求被處理的過程。
本文從宏觀角度介紹了:
原文連結:http://lessisbetter.site/2019/08/19/etcd-raft-sources-arch/
本文作者:大彬,原創授權發布