【go共識算法】-Raft

介紹

Raft 狀態

一個 Raft 集羣包含若干個服務器節點;一般是 5 個,這容許整個系統容忍 2 個節點的失效,每一個節點處於如下三種狀態之一:git

  • follower :全部結點都以 follower 的狀態開始。若是沒收到leader消息則會變成 candidate狀態。
  • candidate:會向其餘結點「拉選票」,若是獲得大部分的票則成爲leader。這個過程就叫作Leader選舉(Leader Election)。
  • leader:全部對系統的修改都會先通過leader。

Raft 一致性算法

Raft經過選出一個leader來簡化日誌副本的管理,例如,日誌項(log entry)只容許從leader流向follower。github

基於leader的方法,Raft算法能夠分解成三個子問題:算法

  • Leader election (領導選舉):原來的leader掛掉後,必須選出一個新的leader
  • Log replication (日誌複製):leader從客戶端接收日誌,並複製到整個集羣中
  • Safety (安全性):若是有任意的server將日誌項回放到狀態機中了,那麼其餘的server只會回放相同的日誌項
Leader election (領導選舉)

Raft 使用一種心跳機制來觸發領導人選舉。當服務器程序啓動時,他們都是 follower(跟隨者) 身份。若是一個跟隨者在一段時間裏沒有接收到任何消息,也就是選舉超時,而後他就會認爲系統中沒有可用的領導者而後開始進行選舉以選出新的領導者。要開始一次選舉過程,follower 會給當前term加1而且轉換成candidate狀態。json

而後他會並行的向集羣中的其餘服務器節點發送請求投票的 RPCs 來給本身投票。候選人的狀態維持直到發生如下任何一個條件發生的時候.緩存

  • 他本身贏得了此次的選舉安全

    • 若是這個節點贏得了半數以上的vote就會成爲leader,每一個節點會按照first-come-first-served的原則進行投票,而且一個term中只能投給一個節點, 這樣就保證了一個term最多有一個節點贏得半數以上的vote
    • 當一個節點贏得選舉, 他會成爲leader, 而且給全部節點發送這個信息, 這樣全部節點都會回退成follower。
  • 其餘的服務器成爲領導者,若是在等待選舉期間,candidate接收到其餘server要成爲leader的RPC,分兩種狀況處理:服務器

    • 若是leader的term大於或等於自身的term,那麼改candidate 會轉成follower 狀態
    • 若是leader的term小於自身的term,那麼會拒絕該 leader,並繼續保持candidate 狀態
  • 一段時間以後沒有任何一個獲勝的人數據結構

    • 有可能,不少follower同時變成candidate,致使沒有candidate能得到大多數的選舉,從而致使沒法選出leader。當這個狀況發生時,每一個candidate會超時,而後從新發增長term,發起新一輪選舉RPC。須要注意的是,若是沒有特別處理,可能出致使無限地重複選主的狀況。
    • Raft採用隨機定時器的方法來避免上述狀況,每一個candidate選擇一個時間間隔內的隨機值,例如150-300ms,採用這種機制,通常只有一個server會進入candidate狀態,而後得到大多數server的選舉,最後成爲主。每一個candidate在收到leader的心跳信息後會重啓定時器,從而避免在leader正常工做時,會發生選舉的狀況。
Log replication (日誌複製)

當選出 leader 後,它會開始接受客戶端請求,每一個請求會帶有一個指令,能夠被回放到狀態機中。leader 把指令追加成一個log entry,而後經過AppendEntries RPC並行的發送給其餘的server,當該entry被多數派server複製後,leader 會把該entry回放到狀態機中,而後把結果返回給客戶端。動畫

當 follower 宕機或者運行較慢時,leader 會無限地重發AppendEntries給這些follower,直到全部的follower都複製了該log entry。ui

Raft的log replication保證如下性質(Log Matching Property):

  • 若是兩個log entry有相同的indexterm,那麼它們存儲相同的指令
  • 若是兩個log entry在兩份不一樣的日誌中,而且有相同的indexterm,那麼它們以前的log entry是徹底相同的

其中特性一經過如下保證:

  • leader在一個特定的termindex下,只會建立一個log entry
  • log entry不會改變它們在日誌中的位置

特性二經過如下保證:

  • AppendEntries會作log entry的一致性檢查,當發送一個AppendEntriesRPC時,leader會帶上須要複製的log entry前一個log entry的(index, iterm),若是follower沒有發現與它同樣的log entry,那麼它會拒絕接受新的log entry 這樣就能保證特性二得以知足。

動畫演示Raft流程

http://thesecretlivesofdata.c...

go實現Raft

項目地址

https://github.com/goraft/raft

數據結構

https://blog.csdn.net/xiongwe...

goraft主要抽象了server、peer和log三個結構,分別表明服務節點、Follower節點和日誌。

server

Raft做爲一種多節點狀態一致性維護協議,運行過程當中必然涉及到多個物理節點,server就是用來抽象其中的每一個節點,維護節點的狀態信息。其結構以下:

type server struct {
    *eventDispatcher

    name        string
    path        string
    state       string          // 每一個節點老是處於如下狀態的一種:follower、candidate、leader
    transporter Transporter
    context     interface{}
    currentTerm uint64          // Raft協議關鍵概念,每一個term內都會產生一個新的leader

    votedFor   string
    log        *Log
    leader     string
    peers      map[string]*Peer // raft中每一個節點須要瞭解其餘節點信息,尤爲是leader節點
    mutex      sync.RWMutex
    syncedPeer map[string]bool  // 對於leader來講,該成員記錄了日誌已經被sync到了哪些follower

    stopped           chan bool
    c                 chan *ev  // 當前節點的命令通道,全部的命令都經過該channel來傳遞
    electionTimeout   time.Duration
    heartbeatInterval time.Duration

    snapshot *Snapshot

    // PendingSnapshot is an unfinished snapshot.
    // After the pendingSnapshot is saved to disk,
    // it will be set to snapshot and also will be
    // set to nil.
    pendingSnapshot *Snapshot

    stateMachine            StateMachine
    maxLogEntriesPerRequest uint64
    connectionString string
    routineGroup sync.WaitGroup
}

log

log是Raft協議的核心,Raft使用日誌來存儲客戶發起的命令,並經過日誌內容的同步來維護多節點上狀態的一致性。

// A log is a collection of log entries that are persisted to durable storage.
type Log struct {
    ApplyFunc   func(*LogEntry, Command) (interface{}, error)    // 日誌被應用至狀態機的方法,這個應該由使用raft的客戶決定
    file        *os.File // 日誌文件句柄
    path        string   // 日誌文件路徑
    entries     []*LogEntry  // 內存日誌項緩存
    commitIndex uint64  // 日誌提交點,小於該提交點的日誌均已經被應用至狀態機
    mutex       sync.RWMutex
    startIndex  uint64  // 日誌中起始日誌項的index
    startTerm   uint64  // 日誌中起始日誌項的term
    initialized bool
}

log entry

log entry是客戶發起的command存儲在日誌文件中的內容

// 編碼後的日誌項包含Index、Term,原始Command的名稱以及Command具體內容
type LogEntry struct {
    Index            *uint64 `protobuf:"varint,1,req" json:"Index,omitempty"`
    Term             *uint64 `protobuf:"varint,2,req" json:"Term,omitempty"`
    CommandName      *string `protobuf:"bytes,3,req" json:"CommandName,omitempty"`
    Command          []byte  `protobuf:"bytes,4,opt" json:"Command,omitempty"`
    XXX_unrecognized []byte  `json:"-"`
}

// A log entry stores a single item in the log.
// LogEntry是日誌項在內存中的描述結構,其最終存儲在日誌文件是通過protocol buffer編碼之後的信息
type LogEntry struct {
    pb       *protobuf.LogEntry
    Position int64 // position in the log file Position表明日誌項存儲在日誌文件內的偏移
    log      *Log
    event    *ev
}
相關文章
相關標籤/搜索