1. 概述
基于Raft共識算法和強大的領導地位策略,pool service和container service可以通過復制其內部的元數據來實現高可用。通過這種方法實現具有副本能力的服務可以容忍少數副本中的任何一個出現故障。通過將每個服務的副本分布在容災域中,pool service和container service可以容忍合理數量的targets出現故障。
?
2. 架構
一個復制服務(replicated service)
是基于Raft復制日志( replicated log)構建的。該服務將RPCs轉換成狀態查詢和確定性狀態更新操作。在被應用于任何一個服務副本之前,所有狀態的更新操作首先提交到副本日志中。由于Raft保證了日志副本之間的一致性,因此服務副本最終以相同的順序應用相同的更新狀態集,并經歷完全相同的狀態歷史。
在一個復制服務的所有副本當中,只有當前領導者可以處理服務RPCs。一個服務的領導者就是當前Raft的領導者(即此刻任期數最高的)非領導者拒絕所有的服務RPCs,并盡其所知的將客戶端請求重定向當前領導。客戶端會緩存副本服務的地址以及當前的領導者。有時,客戶端可能不會獲得任何有意義的重定向提示,并且可以通過與隨機的一個副本通信來找到當前領導者。
上圖展示了構成一個服務副本的相關模塊。service模塊
通過將RPCs轉換成狀態查詢和確定性狀態更新操作來處理RPCs。Raft模塊
通過與其他副本上的Raft模塊通信,按照Raft協議來實現復制的日志。Raft模塊為service模塊執行狀態查詢和狀態更新提供了方法。Storage模塊
(本例中是持久化內存和文件系統)用于存儲service以及Raft狀態。它使用VOS以原子方式更新存儲在持久化內存中的狀態。
?
3. RPC處理
當RPC請求到達領導者服務時,service模塊的服務線程會接收該請求,并通過執行專門為此類型請求設計的句柄函數(handler function)
來處理該請求。就副本服務而言,一個句柄包括:狀態查詢(比如讀取存儲池屬性)、狀態更新(比如寫入一個新版本的pool map)、以及到其他服務的RPCs(比如發送請求到其他target服務)。一些句柄只涉及到查詢,一些句柄會涉及到更新和查詢,另外一些會涉及以上三種。很少見一種情況是,如果有的話,句柄只涉及到更新而不執行查詢。
句柄必須將所有的更新操作組合到單個日志條目中,然后提交日志條目,并在將更新操作應用于服務狀態之前要等待該日志條目成為可適應的。為每一個更新操作的RPC使用單個日志條目很容易使得每個更新操作的RPC在領導者服務出現崩潰或者領導者地位改變時具有原子性。如果在未來引入的RPCS不滿足這些要求,則需要額外事務回復機制。領導者服務的狀態始終表示該領導者到目前位置處理的所有已完成的更新操作的RPC的效果。
另一方面,查詢操作可以直接從服務的狀態讀取,而無需通過復制的日志。然而,為了確保一個請求可以看到所有已經處理過的RPC的效果,該句柄必須詢問Raft模塊領導地位是否有變動。如果沒有,那么到目前位置,針對該請求的所有查詢都不是過時的。如果領導者失去了其領導地位,那么句柄會終止請求,并將客戶端請求重定向到新的領導者。
針對到其他服務的RPCs這種情況,如果這些請求更新了目標服務的狀態,則必須是等冪的。當領導地位發生變化時,如果客戶端重新發送了服務端請求,那么新的領導者可能也要重新向其他服務發送RPCs。
句柄需要處理合理的并發執行操作。通常,在領導者服務上使用本地鎖可以使RPC的執行線性化。一旦領導者地位發生變化,舊的領導者不再執行任何更新操作,這將導致所有的RPCs執行被終止。因此,新領導者上的RPCs與留在舊領導者上的RPCs的并不沖突。因此,不需要鎖作為服務狀態的一部分進行復制。