Raft 是一種為了管理復制日志的一致性算法,該算法強依賴 Leader 節點的可用性來確保集群數據的一致性,即如果client向leader發起添加日志請求,如果leader回復client提交成功,那么該日志一定被過半數的server所提交,且是確定的不會被其他日志覆蓋。
算法包含2類成員,server和client:
- server:接受client的寫日志請求,該類成員包含3種角色:
- follower:跟隨者,server集群首次啟動時都是follower。
- candidate:候選人,在follower的選舉時間超時后會變為candidate。
- leader:領導者,當server成為candidate后并發起選舉投票請求收到過半數server的投票后會變為leader。
- client:負責發起附加日志請求到server
算法包含兩個過程:leader選舉和日志復制:
1.leader選舉
- 該過程發生在server集群啟動或leader宕機時,server最初都是follower,每個follower都維持一個選舉超時時間,當選舉超時時間到達后,follower會自動晉升為candidate,并發起投票請求給其他server,如果收到過半數server的投票同意響應,則晉升為leader,然后定時發送心跳請求給其他server(周期遠小于選舉超時時間),如果有了新的leader,那么其他server都變為follower,當收到leader的心跳請求后會重置自己的選舉超時時間。
- 每次選舉都會產生一個新的遞增的任期號。
- 在server收到投票請求時,只有對方的最新日志比自己的日志新才會回復同意投票響應給對方,日志用任期號和日志索引來比較日志新舊,先比較任期號,再比較索引,任期號是日志從client發送到leader時該leader的任期號。
- 在一次選舉過程中,每個server最多只能投一次票。
2.日志復制
當選出一個leader后,便能夠為client提供服務啦,即處理client的寫日志請求,所有client的寫請求最終都會發送到leader,leader收到client的寫日志請求后,會執行如下:
- 將日志寫到本地文件;
- 將日志復制給其他follower,其他follower收到日志后寫到本地文件并響應leader寫入成功;
- 當leader收到follower的響應并且已經把日志復制到過半數的server節點(包括自己)后,commit該日志(把日志刷新到最終要保存的磁盤位置),并通知其他follower commit該日志。注意:只有該日志的任期號跟自己的任期號相同才能commit,上個任期號的日志在當前任期leader不能被直接commit,需要當前任期leader在commit當前任期日志時該日志索引之前的日志才會被自動commit。
注意:當server節點宕機后節點數量不會發生變化,即該宕機的server還算做集群的一員,在選舉和復制日志過程中還必須考慮其投票和復制日志響應。
日志處于兩種狀態才是安全的,如下:
- 第一種狀態是被commit的日志,該日志已經占據了日志索引位置,在以后才絕對不會被丟失和覆蓋;
- 第二種狀態是該日志被復制到過半數的server,且該日志任期號與當前leader任期號相同;
第一種狀態無需解釋,針對第二種狀態,即使該日志沒有被commit,然后leader宕機了,那么沒有該日志的server節點不可能選舉成為leader,只有包含該日志的server才能被選舉為leader,在選舉成為leader后在復制日志給其他follower后并且也把自己任期的一條日志復制給過半數server后會commit自己任期的該條日志,那么上一個任期的日志也會被間接commit,注意,raft算法不會單純的commit上一個任期的日志,只能在commit自己任期的日志時間接commit上一個任期日志。下面簡單證明為什么leader把當前任期的日志復制到過半數的follower后相當于日志就是安全的了:
假設有5臺server,包含1臺leader4臺follower,當有2臺follower收到日志后該日志是安全的并且可以commit的,假設leader編號為1,任期號為1,收到日志的2臺follower編號為2、3,沒收到日志的3臺follower編號為4、5,如下(表格第一行的1、2表示日志索引位置,日志內容<t,c>,t表示任期號,c表示日志內容):
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | ||
server 5 |
如果這時server 1宕機了,但集群數量還是5(server節點宕機后也算做集群一員),那么只有比5/2+1=3臺server(包括自己)的日志新才能選舉為leader,所以只有follower 2、3才能成為leader,假設2成為了leader,任期號為2,它會將日志<1,x->1>復制給其他follower,如下:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | |
server 2(master) | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | <1,x->1> | |
server 5 | <1,x->1> |
然后server 2收到client的寫日志請求,收到日志y->1,會把該日志復制給其他follower:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | <2,y->1> |
server 2(master) | <1,x->1> | <2,y->1> |
server 3 | <1,x->1> | <2,y->1> |
server 4 | <1,x->1> | |
server 5 | <1,x->1> |
如果server 2收到了過半數server的寫日志成功響應,假設收到了server 1、3的響應,那么可以commit日志<2,y->1>了,如果通知其他follower commit成功那么之前的日志<1,x->1>也會被間接commit。
也就是說,過半數5/2+1=3,這個數量一定比未收到復制日志<1,x->1>的follower的總和2要大,所以不管有幾臺server宕機(當然不能超過5/2=2臺節點宕機),未收到復制日志<1,x->1>的節點(server 4、5)都不會收到過半數server的投票而選舉為leader(因為它一定沒有比過半數server的日志新),只有包含那條復制到過半數server的日志<1,x->1>的server節點(server 1、2、3)才能選舉成為leader,當其選舉為leader后會把該日志<1,x->1>復制給其他follower,最終還是能成功commit該日志。由此可見,只有過半數5/2+1=3的server節點存活才能保證服務可用,如果server 1、2、3全宕機了那服務本身就不可用了,server 4、5不能提供服務不能接收日志寫服務,也就不存在日志<1,x->1>被覆蓋問題,在server 1、2、3重啟之前永遠不能提供服務。
下面解釋下為什么leader在把上一個任期的日志復制到過半數server后該日志還是不安全的:
假設有5臺server,1臺leader,4臺follower,leader編號為1,任期號位1,假設leader把上一個任期的日志復制給了follower 2,然后宕機了,如下(表格第一行的1、2表示日志索引位置,日志內容<t,c>,t表示任期號,c表示日志內容):
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | ||
server 4 | ||
server 5 |
然后server 5通過server 3、4選舉成為leader,任期號為2,并把一條新日志復制給了server 4,然后又宕機了如下:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | ||
server 4 | <2,y->1> | |
server 5(master) | <2,y->1> |
然后server1通過server2、3選舉成為leader,任期號為3,把日志<1,x->1>復制給了server 3,然后又宕機了,如下:
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | <2,y->1> | |
server 5 | <2,y->1> |
然后server 5又通過server 3、4選舉成為leader,任期號為4,它會把日志<2,y->1>復制給其他follower,復制給server 1、2、3時會覆蓋在索引位置為2的<1,x->1>日志,因為該日志沒有被commit所以可以被覆蓋,如下:
1 | 2 | |
---|---|---|
server 1 | <2,y->1> | |
server 2 | <2,y->1> | |
server 3 | <2,y->1> | |
server 4 | <2,y->1> | |
server 5(master) | <2,y->1> |
這種情況下雖然server 1在成為leader且任期號為3,把日志<1,x->1>復制到了過半數server,即server 1、2、3,該日志最后還是被server 5的<2,y->1>覆蓋了。