Etcd Raft架構設計和源碼剖析1:宏觀架構

2019-08-25     Go語言中文網

序言

Etcd提供了一個樣例contrib/raftexample,用來展示如何使用etcd raft。這篇文章通過raftexample介紹如何使用etcd raft。

raft服務

raftexample是一個分布式KV資料庫,客戶端可以向集群的節點發送寫數據和讀數據,以及修改集群配置的請求,它使用etcd raft保持各集群之間數據的一致性。


etcd raft

etcd raft實現了raft論文的核心,所有的IO(磁碟存儲、網絡通信)它都沒有實現,它做了解耦。

它是一個狀態機,有數據作為輸入,經過當前狀態和輸入,得到確定性的輸出,即每個節點上都是一樣的。


raft應用架構

raft集群會由多個節點組成,客戶端的請求發送給raft leader,再由raft leader通過網絡通信在集群之中對請求達成共識。

集群中的每個節點從架構上都可以分為兩層:

  • 應用層,負責處理用戶請求,數據存儲以及集群節點間的網絡通信,
  • 共識層,負責相同和輸入數據和狀態,生成確定性的、一直的輸出,

共識層由etcd raft負責,應用層要負責業務邏輯,數據存儲和網絡通信不需要應用層實現,而是由不同的模塊負責,應用層負責起銜接存儲存儲和網絡通信即可。


應用層有3個重要組成部分:http API、kv store和raftNode。

http API

每個節點都會啟動一個http API用來接受客戶端請求,它只是接收請求,不對請求做處理。它會把客戶端的寫入請求PUT和查詢請求GET都交給kv store。

對於修改raft集群配置請求,它會生成ConfChange交給raftNode。

kv store

一個kv資料庫服務,它保存有一個kv db,用來存儲用戶數據

  • 對於查詢請求,它直接從db中讀取數據。
  • 對於寫入請求,需要修改用戶數據,這就需要集群節點使用raft對請求達成共識,它把請求傳遞給raftNode。

raftNode

raftNode用來跟etcd raft交互,他需要:

  • 把客戶端的寫請求,修改raft配置的請求交給etcd raft
  • 銜接網絡通信跟etcd raft之間的橋樑,把etcd raft的消息發送出去,或接受到的raft消息交給raft
  • 保存raft的WAL和snapshot。

對於寫請求,它會把請求數據編碼後發送給etcd raft,etcd raft會把寫請求封裝成raft的Propose消息MsgProp,編碼後的數據成為log Entry。因為raft並不關心具體的請求內容,它只需要保證每個集群節點在相同的log index擁有相同的log Entry,即請求即可。

raftNode還會啟動1個http server,用來集群節點之間的通信,傳遞raft消息,讓集群節點達成共識。它與http api是不同的,http api用來接收用戶請求。

raftNode與raft交互

raft模塊內部定義了一個Node接口,它代表了raft集群中的一個raft節點,它是應用層跟共識層交互的接口。

其中有幾個與數據傳遞相關函數的是:

  • Propose:應用層通過此函數把客戶端寫請求傳遞raft。
  • ProposeConfChange:應用層通過此函數把客戶端修改raft集群配置的請求傳遞raft。
  • Step:應用層把收到的raft集群之間通信的消息傳遞給raft。
  • Ready:raft對外的出口只有1個,就是Ready函數,Ready函數返回一個通道,應用層可以從這個通道中讀到raft要輸出的所有數據,這個數據被稱為Ready結構體,包括log entry,集群間的通信消息等。
  • Advance:應用層處理完Ready結構體後,調用Advance通知raft,它已處理完剛讀到的Ready結構體,raft可以根據最新狀態生成下一個Ready結構體。

還有一個ApplyConfChange函數,當Ready結構體中包含修改raft集群配置的log entry時,應用層會調用此函數,把配置應用到raft。

raft架構

瞄完raft應用架構,可以從宏觀角度看一下raft是如何跟應用層對接的。

raft包內部有2個很重要的結構體:node和raft。

node結構體

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算法的主要實現。

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。

一個寫請求的處理過程

有了上面架構層面的了解,我們從宏觀的角度看一下一個寫請求被處理的過程。

  1. 客戶端把寫請求發給leader節點
  2. leader節點的http api接收請求,並把請求傳遞給kv store,kv sotre把寫請求發送給raftNode,raftNode把寫請求傳遞給raft.node
  3. leader節點的raft.node把寫請求轉化為log entry,並交給raft.raft,raft.raft生成發送給每一個follower的Append消息
  4. leader節點的raft.node取出raft.raft中的Append消息以及其他數據,封裝成Ready傳遞給raft.Node
  5. leader節點的raft.Node把Ready中的entry保存到storage,然後把Ready中的消息,發送給相應的節點
  6. follower節點的raft.Node收到消息,把消息傳遞給raft.node,raft.node退給raft.raft
  7. follower的raft.raft處理Append消息,進行匹配和校驗後,生成Append Response消息和保存log entry
  8. follower的raft.node從raft.raft獲取數據,然後生成Ready傳遞給raft.Node
  9. follower節點的raft.Node把Ready中的entry保存到storage,然後把Ready中的消息,發送給相應的節點
  10. leader節點的raft.Node收到消息,把消息傳遞給raft.node,raft.node退給raft.raft
  11. leader節點的raft.raft處理Append Response消息,然後檢查已經達成半數以上同意的log entry,更新已經被commit的log entry的index
  12. leader節點的raft.raft在創建Append等消息的時候,填寫了已被commited的log index,所以下次在生成消息,並發送給follower後,follower就根據committed log index提交本地的log entry
  13. 無論是leader,還是follower在生成Ready的時候,會包含已經被committed的log entry,這些entry是等待應用到kv store的,raftNode拿到Ready後,會把這些entry取出來,傳遞給kv store,kv store會修改key-value的最新值。

總結

本文從宏觀角度介紹了:

  • 使用etcd raft應用的架構
  • 使用etcd raft應用應當提供哪些功能供raft使用
  • 應用是如何和etcd raft交互的
  • etcd raft涉及到的存儲概念
  • 一個寫請求從客戶端到在節點之間達成一致,應用到狀態機的過程

原文連結:http://lessisbetter.site/2019/08/19/etcd-raft-sources-arch/

本文作者:大彬,原創授權發布

文章來源: https://twgreatdaily.com/zh/udyt0mwBJleJMoPMTI_O.html