MIT 6.5840 Raft Implementation(2B, Log Replication)

2023-07-24 15:00:42

Raft實現思路+細節(2B)

任務分解

2B中最主要的任務就是進行紀錄檔的複製。Raft是一個強領導人的系統,這意味著所有的紀錄檔新增都是由領導人發起的,與之相類似的,還有很多其他的結論(它們都是比較顯然的),讀者可以自行證明。

我們可以這樣地分解複製紀錄檔的過程

  1. 我們首先需要完善Raft結構體的內容。

     

    我們對新加入的內容進行解釋:

    • commitIndex:這代表了每一個Raft中最高的已經提交的紀錄檔下標,它表示對於這個下標以內的所有紀錄檔,我們都已經和領導達成共識。可以想象,它的更新是在收到RPC的時候完成的,因為如果領導人提交了某一條指令,且某個追隨者有這條指令,那麼就可以認為是達成共識的了,後續可以進行應用等操作。

    • lastApplied:這是一個緊跟commitIndex的成員,他指代的是每一個Raft中已經應用到狀態機中的紀錄檔下標的最大值。我們會對每個Raft有一個輪詢(至少我是這麼實現的),如果lastApplied<commitIndex,那麼就把這個區間內的所有指令應用到複製狀態機上。

    • nextIndex(對於領導人):這代表的是對於每個成員,下一組要發的指令的下標的最小值,可以想象它代表著領導人和每個成員在哪些紀錄檔上達成了一致,它的更新應該是在AppenEntriesRPC中實現的。

    • matchIndex(對於領導人):這代表的是,對於每個成員,和領導人達成一致的下標的最大值(當然,你可以期待一般matchIndex=nextIndex-1)。我們通過它來決定要提交到哪一條指令(也即大部分人matchIndex的下界)。

  2. 經過上層伺服器的多次嘗試,我們終於在領導人這裡加了一條紀錄檔:我們需要完成Start()函數,它的引數為一條指令(一個空的interface),返回的是這條指令在領導人這邊的紀錄檔編號,領導人的任期,和這個人是否是領導人。同時,在Start函數中我們也要顯式地向每一個追隨者發一條心跳(其實就是AppendEntriesRpc).

  3. 類似於MakeElection函數,我們需要完成launchAppendEnries()這一函數,並處理回覆資訊。

  4. 我們需要完成AppendEntries()函數,在裡面完成RPCHandler的功能。

  5. 我們需要完成上文中提到的輪詢,對每一個Raft開一個Go程來檢查要不要提交某一些指令,如果是的話,那就加到applyCh內(這個applyCh也需要自己完成)。

實現細節

  1. Start中,我們需要:

    • 增加log

    • 改變lastLogIndex, lastLogTerm

    • 開Go程來傳送資訊:go rf.launchAppendEntries(peerId)

  2. 我們首先需要傳送AppendEntriesArgs,然後需要考慮AppendEntriesReply,具體來說:

    • 需要遵循Rules For All Servers,如果自己的任期變大,不需要處理;如果Reply的任期更高,變成追隨者......(建議參考raft-extended)

    • 如果回覆顯示成功,那麼把nextIndex, matchIndex都增加,並考察commitIndex是否也可以增加(看看是不是有一半的matchIndex)都超過了某一個值

    • 如果回覆顯示失敗,我們就把nextIndex[peerId]減少一,來向下匹配。(在後文中我們會看到對於這一點的明顯優化)

  3. 這是2B中的核心內容,我們在這裡考察論文中的圖示:

     

  4. 這個比較簡單,只要知道「通道」是啥基本就可以了。

注意事項

  • 在上面這張圖中,Reply false的意思是直接返回

  • 這裡第三條的conflicts with的實現需要細細思考。如果接收者的紀錄檔中的每一條都是正確的,那麼不需要截斷(我到2C才發現這個)

  • 對於Rules For All Server而言,如果變成Follower,不需要返回。這一點上的處理是和2A一致的。

關於評測