一個 Raft 集羣包含若干個服務器節點;一般是 5 個,這容許整個系統容忍 2 個節點的失效,每一個節點處於如下三種狀態之一:git
follower
:全部結點都以 follower 的狀態開始。若是沒收到leader
消息則會變成 candidate
狀態。candidate
:會向其餘結點「拉選票」,若是獲得大部分的票則成爲leader。這個過程就叫作Leader選舉(Leader Election
)。leader
:全部對系統的修改都會先通過leader。Raft經過選出一個leader來簡化日誌副本的管理,例如,日誌項(log entry
)只容許從leader流向follower。github
基於leader的方法,Raft算法能夠分解成三個子問題:算法
Leader election
(領導選舉):原來的leader掛掉後,必須選出一個新的leaderLog replication
(日誌複製):leader從客戶端接收日誌,並複製到整個集羣中Safety
(安全性):若是有任意的server將日誌項回放到狀態機中了,那麼其餘的server只會回放相同的日誌項Leader election (領導選舉)
Raft 使用一種心跳機制來觸發領導人選舉。當服務器程序啓動時,他們都是 follower
(跟隨者) 身份。若是一個跟隨者在一段時間裏沒有接收到任何消息,也就是選舉超時,而後他就會認爲系統中沒有可用的領導者而後開始進行選舉以選出新的領導者。要開始一次選舉過程,follower
會給當前term
加1而且轉換成candidate
狀態。json
而後他會並行的向集羣中的其餘服務器節點發送請求投票的 RPCs 來給本身投票。候選人的狀態維持直到發生如下任何一個條件發生的時候.緩存
他本身贏得了此次的選舉安全
first-come-first-served
的原則進行投票,而且一個term中只能投給一個節點, 這樣就保證了一個term最多有一個節點贏得半數以上的vote
。其餘的服務器成爲領導者,若是在等待選舉期間,candidate接收到其餘server要成爲leader的RPC,分兩種狀況處理:服務器
一段時間以後沒有任何一個獲勝的人數據結構
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
有相同的index
和term
,那麼它們存儲相同的指令log entry
在兩份不一樣的日誌中,而且有相同的index
和term
,那麼它們以前的log entry
是徹底相同的其中特性一經過如下保證:
leader在
一個特定的term
和index
下,只會建立一個log entry
log entry
不會改變它們在日誌中的位置特性二經過如下保證:
leader會帶上須要複製的log entry前一個log entry的(index, iterm)
,若是follower沒有發現與它同樣的log entry,那麼它會拒絕接受新的log entry 這樣就能保證特性二得以知足。http://thesecretlivesofdata.c...
https://github.com/goraft/raft
https://blog.csdn.net/xiongwe...
goraft主要抽象了server、peer和log三個結構,分別表明服務節點、Follower節點和日誌。
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是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是客戶發起的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 }