在豬爸爸的努力下,三個銀行網點能正確的選出一個主網點對外提供服務了。一切工作的良好,但隨著客戶的增多,一些問題漸漸暴露出來。
這天,兔小姐又叫來了豬爸爸,說到:『豬爸爸,現在我們碰到了一個問題。每次客戶在 Leader 網點進行一筆交易,我們都需要告訴其他兩個地方,只有我們確定了交易記錄被大多數網點保存了,這筆交易才會真正開始執行,處理完了,我們才能繼續服務下一個客戶。已經有很多客戶投訴我們效率慢了。』
『是的,兔小姐,之前我們設計的機制只能保證整個系統在出現了異常的時候,能夠安全的運行,不會出現金錢不一致的情況。現在,是時候考慮對整個系統進行優化了,而且我已經想到了很多優化的手段。』豬爸爸信心滿滿的說到。
『那真的是太好了』兔小姐高興的說到,『你能跟我好好的說一下嘛?』
『當然可以!』
Pipeline 和 Batch
『首先,我們來整理下現在的流程,假設客戶來存錢,我們會先記錄交易,然后給其他幾個網點發送消息,等我們確定大部分網點都確認了這筆交易記錄,就可以開始執行這筆交易,將用戶的錢存到金庫里面了。』
『是的,豬爸爸』
『現在,為了后面簡單說明,我將整個流程簡化說明一下,其實就是幾個步驟,也就是 Propose,Append,Broadcast 和 Apply。客戶發起交易,我們叫做 Propose,然后我們記錄交易,這個叫做 Append,再就是通知其他網點,這個叫做 Broadcast,等我們最后知道大部分網點都確認了這筆交易記錄,我們就執行交易,也就是 Apply。希望我這么簡化不會讓你困惑,兔小姐。』豬爸爸有點擔憂的詢問到。
『雖然有點抽象了,但我想我還是能理解的,豬爸爸。麻煩你繼續。』
『好的,兔小姐,既然你理解了簡化流程,那么我下面開始說第一個優化,就是 Pipeline。』
『Pipeline?這是什么,你完全把我搞糊涂了,豬爸爸』兔小姐吃驚的說到。
『不要緊張,兔小姐。Pipeline 就是管道,你可以把我們的整個流程想成一個 Pipeline。對于客戶 A,操作是 Propose,Append,Broadcast, Apply,對于客戶 B 也是一樣的流程,之前我們必須等 A 完成了,才能處理 B。但不知道你發現了沒有,兔小姐,當 A 在 Append 之后,我們就可以開始處理 B 的 Propose 了。』
『為什么呢?豬爸爸,我有點不明白了』
『我們只要保證的是所有銀行網點的交易記錄是一致有序的,那么我們就一定能保證最終所有銀行的數據是一致的,所以只要 A Append 了,B 開始 Propose,B Append 的時候交易記錄一定在 A 的后面,這樣記錄就一定是有序的了。』
『我大概有點理解了。』兔小姐說到。
『所以,兔小姐,當 A 執行完 Append 之后,我們就能立刻開始處理 B,而當 B Append 之后,我們也可以立刻處理下一個用戶 C,這樣整個流程就是一個像水流那樣源源不斷流動的了,這不就是一個 Pipeline 了。』
『嗯,真的是很形象,豬爸爸。』兔小姐由衷的贊嘆道。
Batch
『我們還可以繼續優化了,兔小姐。上面我們說到了 Pipeline,我們還可以做 Batch。』豬爸爸繼續說道。
『Batch?』兔小姐疑惑的說道。
『是的,Batch。也就是我們可以將很多單獨的操作合并到一塊處理。』
『哦,這個我明白。』兔小姐說道,『可哪里做 Batch 呢?』
『如果很多客戶同時要發起交易,那么我們可以將這些交易記錄,用一個消息發送過去到其他網點,這樣我們就不需要一個一個的發送消息了,這就是 Batch。』
『哦,我明白了。因為我們各個網點之間距離還是有點遠的,消息傳遞的時間開銷還是有點大的。所以使用 Batch 可以減少消息的發送次數,自然就能提高效率了,是吧,豬爸爸。』
『非常正確,兔小姐。』
Leader 并發寫盤
上面說到了 Pipeline 和 Batch,這里我們再次說明一下整個 Raft 的流程。Leader 收到 Propose,Append Log,然后 Broadcast Log,等收到 Follower 的回復確定 Log 被 Committed 之后,開始 Apply。而對應的 Follower,在收到 Log 之后,先 Append Log,然后給 Leader reply 消息,等下次 Leader 發過來的消息知道 Log 被 Committed 了,就可以 Apply 了,好了,我們繼續說故事吧。
『豬爸爸,有了 Pipeline 和 Batch,我們銀行的服務速度應該會很快了。』
『是的,兔小姐,不過我們還可以繼續優化了。』
『真的?』兔小姐有點不可思議。
『是的,你還記得我之前提到過的 quorum 吧。對于一筆交易記錄,如果我們知道大部分銀行網點都確認了這筆記錄,我們就可以繼續處理交易了。』
『當然記得,豬爸爸。』
『當用戶在 Leader 網點進行交易的時候,原來我們的流程是 Propose,Append,然后在 Broadcast,但現在,我們在 Propose 之后,就可以直接 Broadcast,同時 Append,這兩個步驟在 Leader 網點是可以同時處理的。』
『同時處理?豬爸爸,這沒什么風險吧?』
『不會有任何風險,即使 Broadcast 先進行,Leader 網點這邊仍然需要在 Append 之后確保這筆記錄被大多數網點確認了。』
『我想我有點明白了。也就是這兩個流程其實不相關,反正無論怎樣,后面 Leader 都必須等待 Follower 回復的確認消息,才能最終確定這筆交易是否已經被大多數網點接受了。』
『對的,兔小姐。但這里我們需要注意,這個優化只能在 Leader 網點進行,在 Follower 網點這邊,我們仍然需要保證先 Append,再回復。』
『哦,這又是怎么回事,豬爸爸?』
『我們上面說了,Leader 網點必須知道大部分網點都收到了交易記錄,才能認為是 Committed,然后繼續處理。如果 Follower 網點這邊也直接先回復消息,在 Append,就可能出現一種情況,在 Append 之前,Follower 網點出現了問題,導致 Append 不成功。那么極端情況下面就會出現,Leader 認為記錄都已經被大部分節點接受了,但實際并沒有,我們就很可能面臨數據丟失的問題了。』
『好復雜,但我貌似懂了。』兔小姐思索了一會說到。
小結
Raft 的 Paper 其實比較簡單,但如果實際按照 Paper 實現,會發現性能并不好,所以在實踐中,我們會做很多優化,上面提到的只是一些優化手段,還有一些,譬如 Leader 在發送一次 Log 之后不需要等 Follower 的回復,繼續發送后面的 Log,Follower 只需要回復最近的一次 Log Index 或者 reject 就可以。
這些優化方式在 etcd 和 TiKV 里面都有,大家可以自己去瀏覽代碼。