1.概述
cornerstone中msg主要為resp_msg,req_msg型別。其中涉及到了oop中程式碼複用與封裝、繼承等優秀的設計思想,值得解析。
2.msg_base程式碼分析
class msg_base
{
private:
ulong term_;
msg_type type_;
int32 src_;
int32 dst_;
}
- 首先資訊從一方傳送到另一方,所以需要
src_
,dst_
- 在raft背景下,每條資訊還需記錄任期
term_
,同時為了支援不同訊息,有訊息型別type_
3. req_msg程式碼分析
class req_msg : public msg_base
{
private:
ulong last_log_term_;
ulong last_log_idx_;
ulong commit_idx_;
std::vector<ptr<log_entry>> log_entries_;
}
- 為了複用程式碼,public繼承msg_base,訊息自帶
src_
,dst_
,term_
,type_
- raft裡面資訊都是單向流動,只能從leader到follower。為了同步leader與follower的entry,需要leader的
last_log_term_
與last_log_idx_
以及ulong commit_idx_
。 - 具體leader的entry放在
log_entries_
裡面,如果不是為了同步entry可以為空
4. resp_msg程式碼分析
class resp_msg : public msg_base
{
private:
ulong next_idx_;
bool accepted_;
}
這裡next_idx_的存在在append-entry這一個rpc通訊裡面很重要
在append-entry裡面,follower對leader的rpc請求有兩種情況
- follower接受leader的entry
- follower不接受leader的entry
如果是accept的情況,那麼next_idx_其實是多餘的,
但是如果不是accept,那麼next_idx_就發揮作用了。
在raft論文裡面,一個leader對每個follower都記錄next_idx與match_idx。
next_idx:leader對follower應該match的idx的猜測(初始化為leader的log_store的最後idx + 1)
match_idx:leader與follower的log_store重合(即match)的idx的最大值,為實際值
舉例來說:
leader:[1,2,3,4]
follower A:[1,2,3,7]
follower B:[1,3]
那麼A的match_idx為2, B的match_idx為0
剛開始leader對A,B的對match_idx的猜測值next_idx都為4,即為leader的log_store的最後idx + 1。
在raft論文裡給出調整next_idx的方法為next_idx每次減1,但是顯然這是可以最佳化的。
因為在append-entry rpc中透過follower的resp,leader可以得知每個follower的log_store情況,不需要每次減1,可以一步到位。
比如對B來說,leader可以將B的next_idx縮減成B的log_store的最後idx + 1 = 2再繼續逼近。
(對於A來說還是得每次減1,因為A的log_store中entry數目跟leader一樣多。換句話說這種最佳化只針對follower的entry數目小於leader的情況。)
綜上所述,在resp裡面我們還需要next_idx_來幫助leader來糾正next_idx,使其更快逼近match_idx。
5.總結
- 1.善於設計base類,利用類的繼承實現base類的擴充。
- 2.在rpc通訊中,學會利用resp來傳遞follower的訊息實現最佳化。