# 云計算設計模式(十三)——領導人選舉模式
通過協調合作,在分布式應用程序的任務實例集合執行的操作,選舉一個實例作為承擔管理的其他實例責任的領導者。這個模式可以有助于確保任務實例不互相沖突,導致爭用共享資源,或與其他的任務實例正在執行的工作無意中干擾。
## 背景和問題
一個典型的云應用包括行動協調的方式很多任務。這些任務都可以是實例運行相同的代碼和需要訪問相同的資源,或者它們可能是可并行工作,以執行復雜計算的各個部分。
任務實例可能為多的時間自主運行,但它也可能是必要的,以協調各實例的操作,以確保它們不發生沖突,導致爭用共享資源,或無意中妨礙工作,其他的任務實例正在執行。例如:
- 在基于云的系統,該系統實現了橫向擴展,同一個任務的多個實例可以與每個實例服務于不同的用戶同時運行。如果這些實例寫入到共享資源,也可能是必要的,以協調它們的操作,以防止每個實例從盲目地覆蓋由他人進行的更改。
- 如果任務正在執行復雜的計算以并行的單個元素,其結果將需要被聚合時,他們都完成了。
由于任務實例都是同行,沒有天生的領導者,能夠充當協調員或聚合器。
## 解決方案
單個任務實例應選充當領導者,這種情況下應協調其他從屬任務實例的操作。如果所有的任務實例都運行相同的代碼,他們可能都能夠充當領導者。因此,選舉過程必須謹慎管理,以防止兩個或多個實例接管領導者的角色在同一時間。
該系統必須選擇的領導者提供了一個堅固的機構。這種機制必須能夠應付諸如網絡中斷或進程故障事件。在許多解決方案中,下屬的工作情況監控的領導者,通過某種類型的心跳機制,或通過輪詢。如果指定的領導者意外終止或網絡故障使得領導者不通下屬的工作情況,有必要為他們選出了新的領導人。
有可用于選舉的領導者之間的一組任務在分布式環境中,包括幾個策略:
- 與排名最低的實例或進程ID選擇任務實例。
- 競速獲得一個共享的分布式互斥。第一個任務實例獲取該互斥鎖處于領先地位。然而,系統必須保證,如果領導者終止或變得與系統的其余部分斷開,該互斥被釋放,以允許另一個任務實例成為領導者。
- 實施的共同領導人選舉算法,如惡霸算法或環算法之一。這些算法是相對簡單的,但也有一些更復雜的技術提供。這些算法假定每個候選參選具有唯一的 ID,并且,它們可以用可靠的方式在其他候選人進行通信。
## 問題和注意事項
在決定如何實現這個模式時,請考慮以下幾點:
- 選出一個領導者的過程應該是彈性的瞬時和永久失效。
- 必須能夠檢測到當領導失敗,或變成不可用(可能是由于通訊故障)。在這是需要這種檢測的速度將取決于系統。有些系統可能能夠發揮作用了一會兒沒有一個領導者,在此期間造成的領導人變得不可用瞬時故障可能已被排除。在其他情況下,可能有必要立即檢測領袖失敗并引發新的選舉。
- 在實現自動縮放水平的系統中,如果系統鱗背面和關閉一些計算資源的領導者可能被終止。
- 使用一個共享的分布式互斥引入外部服務,提供了互斥鎖的可用性依賴。該服務可以構成一個單點故障。如果此服務應該以任何理由變得不可用時,系統將無法選出一個領導者。
- 使用一個專用進程的領導者是一個比較簡單的方法。然而,如果該過程失敗,可能有顯著延遲而被重新啟動,并且將得到的延遲可能影響其他進程的性能和響應時間,如果他們正在等待領導人來協調的動作。
- 實施的領導人選舉算法之一手動為調整和優化代碼的最大靈活性。
## 何時使用這個模式
使用此模式時,在分布式應用程序的任務,比如云托管解決方案,需要認真協調,也沒有天生的領導者。
注意:避免使領導者在系統中的瓶頸。領導者的目的是協調的附屬任務完成的工作,它不一定有機會參加這項工作本身,盡管它應該是有能力這樣做,如果任務沒有當選領導人。
這種模式可能不適合:
- 如果有一個天生的領導者或專用的過程,可以隨時充當領導者。例如,有可能實現一個單進程,其協調該任務的實例。如果這個過程失敗或變得不健康,系統可以將其關閉并重新啟動它。
- 如果任務之間的協調,可以很容易地通過使用更輕便的機構來實現的。例如,如果幾個任務實例僅僅需要對共享資源的訪問協調,一個最好的解決辦法可能是使用樂觀或悲觀鎖來控制訪問該資源。
- 如果一個第三方的解決方案是比較合適的。例如,微軟的 Azure HDInsight 服務(基于 Apache Hadoop 的)使用所提供的 ApacheZookeeper 協調的 map / reduce 的匯總任務和匯總數據的服務。它也可以安裝并在Azure的虛擬機配置動物園管理員,并將其集成到自己的解決方案,或使用可從微軟開放技術的動物園管理員預置的虛擬機映像。欲了解更多信息,請參閱 Apache 的動物園管理員在微軟的 Azure 在微軟開放技術的網站。
## 例子
在列入可用于本指南中的示例代碼中 LeaderElection 解決方案 DistributedMutex 項目展示了如何使用租賃在 Azure 存儲 BLOB 提供了一種機制,實現共享的分布式互斥。此互斥鎖可以用來選擇在 Azure 云服務的領導者之間的一組角色的實例。第一個角色實例獲得租約當選的領導人,并保持領先直至其租賃或直到它無法續租。其他角色實例可以繼續監視在領導不再可用的情況下將 BLOB 租賃。
### 注意
一個 BLOB 租賃是在一個 blob 的排他寫鎖。單個 BLOB 可以是一整租的在任何一個時間點的問題。角色實例可以請求租約在指定的斑點,而且將被授予租約,如果沒有其他租賃在同一個斑點,是由這個或任何其他作用,比如舉行,否則將拋出一個異常。
為了減少一個故障角色實例保留無限期租用的可能性,指定了一輩子的租約。當此期滿后,租賃變為可用。然而,當一個角色實例持有的租賃也可以請求租約到期,并且將被授予租約的時間再延長。角色實例可以不斷重復這一過程,如果它希望保留租約。
有關如何租用一個 blob 的詳細信息,請參閱租賃斑點(REST API)在 MSDN 上。
~~~
public class BlobDistributedMutex
{
...
private readonly BlobSettings blobSettings;
private readonly Func<CancellationToken, Task> taskToRunWhenLeaseAcquired;
...
?
public BlobDistributedMutex(BlobSettings blobSettings,
Func<CancellationToken, Task> taskToRunWhenLeaseAquired)
{
this.blobSettings = blobSettings;
this.taskToRunWhenLeaseAquired = taskToRunWhenLeaseAquired;
}
?
public async Task RunTaskWhenMutexAcquired(CancellationToken token)
{
var leaseManager = new BlobLeaseManager(blobSettings);
await this.RunTaskWhenBlobLeaseAcquired(leaseManager, token);
}
...
~~~
代碼示例中的 RunTaskWhenMutexAquired 上述方法調用以下代碼示例來實際獲得的租賃所示的 RunTaskWhenBlobLeaseAcquired 方法。該 RunTaskWhenBlobLeaseAcquired 方法異步運行。如果租賃的成功獲取,角色實例已經當選的領導者。該 taskToRunWhenLeaseAcquired 委托的目的是為了執行協調其它角色實例的工作。如果未取得租賃,另一個角色實例已當選為領導人和當前角色實例仍然是一個下屬。注意,TryAcquireLeaseOrWait 方法是使用 BlobLeaseManager 對象獲取租賃一個輔助方法。
~~~
...
private async Task RunTaskWhenBlobLeaseAcquired(
BlobLeaseManager leaseManager, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
// Try to acquire the blob lease.
// Otherwise wait for a short time before trying again.
string leaseId = await this.TryAquireLeaseOrWait(leaseManager, token);
?
if (!string.IsNullOrEmpty(leaseId))
{
// Create a new linked cancellation token source so that if either the
// original token is cancelled or the lease cannot be renewed, the
// leader task can be cancelled.
using (var leaseCts =
CancellationTokenSource.CreateLinkedTokenSource(new[] { token }))
{
// Run the leader task.
var leaderTask = this.taskToRunWhenLeaseAquired.Invoke(leaseCts.Token);
...
}
}
}
}
...
~~~
由領導開始的任務還異步執行。雖然這個任務正在運行,下面的代碼示例所示的 RunTaskWhenBlobLeaseAquired方法周期性地嘗試續訂租約。這個動作有助于確保該角色實例保持領先。在簡單解決方案中,續訂請求之間的延遲小于對租賃期限,以防止另一角色實例當選的領導人指定的時間。如果更新失敗,以任何理由,任務被取消。如果租用未能被更新或任務被取消(可能為角色實例關停的結果),租賃被釋放。在這一點上,這個或另一個角色實例可能被選為領導者。下面的代碼提取物顯示出此過程的一部分。
~~~
...
private async Task RunTaskWhenBlobLeaseAcquired(
BlobLeaseManager leaseManager, CancellationToken token)
{
while (...)
{
...
if (...)
{
...
using (var leaseCts = ...)
{
...
// Keep renewing the lease in regular intervals.
// If the lease cannot be renewed, then the task completes.
var renewLeaseTask =
this.KeepRenewingLease(leaseManager, leaseId, leaseCts.Token);
?
// When any task completes (either the leader task itself or when it could
// not renew the lease) then cancel the other task.
await CancelAllWhenAnyCompletes(leaderTask, renewLeaseTask, leaseCts);
}
}
}
}
...
}
~~~
該 KeepRenewingLease 方法是使用 BlobLeaseManager 對象續租另一個 helper 方法。該 CancelAllWhenAnyCompletes 方法取消指定為前兩個參數的任務。
圖 1 示出了 BlobDistributedMutex 類的功能。

圖1 - 使用 BlobDistributedMutex 類選出一個領導者和運行協調操作的任務
下面的代碼示例顯示了如何使用 BlobDistributedMutex 類的輔助角色。此代碼獲取租賃了一個名為 MyLeaderCoordinatorTask 在開發的倉儲租賃容器中的 blob,并指定在 MyLeaderCoordinatorTask 方法定義的代碼應該運行,如果角色實例當選的領導人。
~~~
var settings = new BlobSettings(CloudStorageAccount.DevelopmentStorageAccount,
"leases", "MyLeaderCoordinatorTask");
var cts = new CancellationTokenSource();
var mutex = new BlobDistributedMutex(settings, MyLeaderCoordinatorTask);
mutex.RunTaskWhenMutexAcquired(this.cts.Token);
...
?
// Method that runs if the role instance is elected the leader
private static async Task MyLeaderCoordinatorTask(CancellationToken token)
{
...
}
~~~
請注意有關樣品溶液中的以下幾點:
- BLOB 是一個潛在的單點故障。如果 Blob 服務不可用,或的 blob 是人跡罕至,領導者將無法續租,并沒有其他的作用,比如將能夠獲得租約。在這種情況下,沒有作用,例如將能夠充當領導者。然而,blob 服務被設計為彈性的,所述 blob 的服務,以便徹底失敗被認為是極不可能的。
- 如果被領導者攤位正在執行的任務,領導者可能會繼續續租,防止任何其他角色實例從獲得租約,并接管了領導作用,以協調任務。在現實世界中,領導者的健康應該頻繁地進行檢查。
- 在選舉過程具有不確定性。你不能做任何假設哪個角色實例將得到的blob租約,成為領導者。
- 不應使用任何其他目的用作的 blob 租賃的目標的 blob。如果一個角色實例嘗試將數據存儲在該的 blob,該數據將不能被訪問,除非該角色實例是領導者和持有的 blob租約。
- 前言
- (一)—— 緩存預留模式
- (二)—— 斷路器模式
- (三)—— 補償交易模式
- (四)——消費者的競爭模式
- (五)——計算資源整合模式
- (六)——命令和查詢職責分離(CQRS)模式
- (七)——事件獲取模式
- (八)——外部配置存儲模式
- (九)—— 聯合身份模式
- (十)——守門員模式
- (十一)—— 健康端點監控模式
- (十二)—— 索引表模式
- (十三)——領導人選舉模式
- (十四)——實體化視圖模式
- (十五)—— 管道和過濾器模式
- (十六)——優先級隊列模式
- (十七)—— 基于隊列的負載均衡模式
- (十八)—— 重試模式
- (十九)——運行重構模式
- (二十)—— 調度程序代理管理者模式
- (二十一)——Sharding 分片模式
- (二十二)——靜態內容托管模式
- (二十三)——Throttling 節流模式
- (二十四)—— 仆人鍵模式