Elasticell-聊聊Raft的優化

以前兩篇文章介紹了作Elasticell的原因和Multi-Raft的實現細節:git

這篇主要介紹Elasticell在Raft上作的一些優化工做。異步

 

一次簡單的Raft就一個值達成一致的過程性能

  1. Leader收到一個請求優化

  2. Leader存儲這個Raft Log,而且給Follower發送AppendEntries消息spa

  3. Follower收到而且存儲這個Raft Log,給Leader回覆AppendEntries消息線程

  4. Leader收到大多數的Follower的回覆,給Follower發送AppendEntries消息,確認這個Log已經被Commit了進程

  5. Leader本身Apply這個Log事件

  6. Leader Apply完成後,回覆客戶端

  7. Follower收到Commit的AppendEntries消息後,也開始Apply這個Log

若是都順序處理這些,性能就可想而知了,下面咱們來看一下在Elasticell中針對Raft所作的一些優化工做。

 

Append Log並行化

2這個步驟,Leader能夠AppendEntries消息和Raft Log的存儲並行。爲何?

  1. 若是Leader沒有Crash,那麼和順序的處理結果一致。

  2. 若是Leader Crash了,那麼若是大於n/2+1的follower收到了這個消息並Append成功,那麼這個Raft Log就必定會被Commit,新選舉出來的Leader會響應客戶端;不然這個Raft Log就不會被Commit,客戶端就會超時/錯誤/或重試後的結果(看實現方式)。

 

異步Apply

一旦一個Log被Committed,那麼何時被Apply都不會影響正確性,因此能夠異步的Apply這個Log。

 

物理鏈接多路複用

當系統中的Raft-Group愈來愈多的時候,每一個Raft-Group中的全部副本都會兩兩建鏈,從物理上看,最後會看到2臺物理機器(能夠是物理機,虛機,容器等等)之間會存在大量的TCP連接,形成連接爆炸。Elasticell的作法:

  1. 使用鏈路複用技術,讓單個Store進程上全部的Raft-Group都複用一個物理連接

  2. 包裝Raft消息,增長Header(Header中存在Raft-Group的元信息),這樣在Store收到Raft消息的時候,就可以知道這些消息是屬於哪個Raft-Group的,從而驅動Raft。

 

Batching & Pipelining

不少同窗會認爲實現強一致存儲會影響性能,其實並不是如此,在合理的優化實現下,強一致存儲對於系統的吞吐量並不會有多大的影響,這主要來自於一致性協議的兩個重要的細節Batching和Pipelining,理念能夠參見論文[1],事實上,在阿里近期反覆提到的X-DB跨機房優化中也實現了相似的功能X-Paxos,所以下面看看Raft的Batching和Pipelining如何在Elasticell中達到相似的效果。

 

在Elasticell中Batching在各個階段都有涉及,Batching能夠提升系統的吞吐量(和Latency矛盾)。Elasticell中的單個Raft-Group使用一個Goroutine來處理Raft事件,好比Step,Request,Raft Ready,Tick,Apply Result等等。

  • Proposal階段,收集在上一次和本次處理Raft事件之間的全部請求,並把相同類型的請求作合併,並作一個Proposal,減小Raft的網絡請求

  • Raft Ready階段, 收集在上一次和本次處理Raft事件之間的全部的Ready信息,Leader節點Batch寫入Raft Log

  • Apply階段,因爲Apply是異步處理,能夠把相同類型的操做合併Apply(例如把多個Redis的Set操做合併爲一個MSet操做),減小CGO調用

Raft的Leader給Follower發送AppendEntries的時候,若是等待上一次的AppendEntries返回,再發下一個AppendEntries,那麼必然性能不好。因此須要作Pipelining來加速,不等上一次的AppendEntries返回,持續的發送AppendEntries。

 

若是要保證性能和正確性,須要作到如下兩點:

  1. Leader到某一個Follower之間的發送管道必須是有序的,保證Follower有序的處理AppendEntries。

  2. 可以處理丟失AppendEntries的情況,好比連續發送了Index是2,3,4的三個Append消息,其中3這個消息丟包了,Follower收到了2和4,那麼Leader必須從新發送3,4兩個Append消息(由於4這個消息會被Follower丟棄)。

對於第二點,Etcd的庫已經作了處理,在Follower收到Append消息的時候,會檢查是否是匹配已經接收到的最後一個Raft Log,若是不匹配,就返回Reject消息,那麼按照Raft協議,Leader收到這個Reject消息,就會從3(4-1)重試。

 

Elasticell的實現方式:

  1. 保證用於發送Raft消息的連接在每兩個節點直接只有一個

  2. 把當前節點待發送的Raft消息按照對端節點的ID作簡單的hash,放到不一樣的線程中去,由這些線程負責發送(線程的數量就至關於Pipelining的管道數)

這樣就能保證每一個Follower收到的Raft消息是有序的,而且每一個Raft都只有一個Goroutine來處理Raft事件,這些消息可以保證被順序的處理。

Batching和Pipelining的trade off

Batching可以提升系統的吞吐量(會帶來系統Latency增大),Pipelining可以下降系統的Latency(也能在必定程度上提升吞吐量),這個2個優化在決策的時候是有衝突的(在Pipelining中發送下一個請求的時候,須要等多少的Batch Size,也許多等一會就回收集更多的請求),目前Elasticell採用的方式是在不影響Pipelining的前提下,儘量多的收集2次Pipelining之間的請求Batching處理策略,顯然這並非一個最優的解決方案。

尚未作的優化

以上是Elasticell目前已經作的一些優化,還有一些是將來須要作的:

  1. 不使用RocksDB存儲Raft Log,因爲Raft Log和RocksDB的WAL存在功能重複的地方,這樣就多了一次文件IO

  2. Raft的heartbeat合併,當一個節點上的Raft-Group的不少的時候,heartbeat消息過多

  3. Batching Apply的時候,當前節點上全部正在Apply的Raft-Group一塊兒作Batching而不是在一個Raft-Group上作Batching

  4. 更高效的Batching和Pipelining模式,參考論文[1]

瞭解更多

https://github.com/deepfabric/elasticell

參考

[1] Tuning Paxos for high-throughput with batching and pipelining

相關文章
相關標籤/搜索