藉由選擇一個實例做為負責管理其他人的領導者,協調分散式應用程式中共同作業實例集合所執行的動作。 這有助於確保實例不會彼此衝突、造成共用資源的爭用,或不小心干擾其他實例正在執行的工作。
內容和問題
典型的雲端應用程式有許多工作會以協調方式運作。 這些工作全都可以是執行相同程式碼並要求存取相同資源的實例,或者它們可能會平行運作,以執行複雜計算的個別部分。
工作實例可能會在大部分時間分別執行,但可能也需要協調每個實例的動作,以確保它們不會衝突、造成共用資源的爭用,或不小心干擾其他工作實例正在執行的工作。
例如:
- 在實作水平調整的雲端式系統中,相同工作的多個實例可以同時執行,且每個實例都為不同的使用者提供服務。 如果這些實例寫入共用資源,就必須協調其動作,以防止每個實例覆寫其他人所做的變更。
- 如果工作會以平行方式執行複雜計算的個別元素,則必須在結果全部完成時匯總。
工作實例都是同儕節點,因此沒有可作為協調器或匯總工具的自然領導者。
解決方案
應該選取單一工作實例作為領導者,而且此實例應該協調其他次級工作實例的動作。 如果所有工作實例都執行相同的程式代碼,則每個實例都能夠做為領導者。 因此,必須謹慎管理選舉程式,以防止兩個或多個實例同時接管領導人職位。
系統必須提供健全的機制來選取領導者。 此方法必須處理網路中斷或進程失敗等事件。 在許多解決方案中,次級工作實例會透過某種類型的活動訊號方法或輪詢來監視領導者。 如果指定的領導者意外終止,或網路失敗會使領導者無法供次級工作實例使用,則必須讓他們選擇新的領導者。
在分散式環境中的一組工作之間,有多個選擇領導者的策略,包括:
- 競相取得共用分散式 Mutex。 取得 Mutex 的第一個工作實例是領導者。 不過,系統必須確定,如果領導者終止或與系統的其餘部分中斷連線,則會釋放 mutex,以允許另一個工作實例成為領導者。 此策略示範於下列範例中。
- 實作其中一種常見的領導者選舉演算法,例如 欺負演算法、 木舟共識演算法或 環形演算法。 這些演算法假設選舉中的每個候選人都有唯一的標識碼,而且它可以可靠地與其他候選人通訊。
問題和考量
當您決定如何實作此模式時,請考慮下列幾點:
- 選擇領導者的程式應該能夠復原暫時性和持續性失敗。
- 必須能夠偵測領導者何時失敗或變成無法使用(例如通訊失敗)。 系統相依,需要多快的偵測。 某些系統可能會在沒有領導者的情況下短暫運作,在此期間可能會修正暫時性錯誤。 在其他情況下,可能需要立即偵測領導者失敗並觸發新的選舉。
- 在實作水平自動調整的系統中,如果系統相應減少並關閉部分運算資源,則領導者可能會終止。
- 使用共用分散式 Mutex 引進了對提供 Mutex 的外部服務相依性。 服務構成單一失敗點。 如果因為任何原因而無法使用,系統將無法選出領導者。
- 以領導者身分使用單一專用程式是一種直接的方法。 不過,如果進程失敗,重新啟動時可能會有顯著的延遲。 如果導致延遲等待領導者協調作業,則所產生的延遲可能會影響其他進程的效能和回應時間。
- 手動實作其中一個領導者選舉演算法,可提供微調和優化程序代碼的最大彈性。
- 避免讓領導者成為系統中的瓶頸。 領導的目的是協調次級工作的工作,而且不一定必須參與這項工作本身,但如果任務不當選為領導,它應該能夠這樣做。
使用此模式的時機
當分散式應用程式中的工作,例如雲端裝載的解決方案,需要仔細協調,而且沒有自然領導者時,請使用此模式。
如果:
- 有一個自然領導者或專用的程序,隨時可以做為領導者。 例如,您可以實作協調工作實例的單一進程。 如果此程式失敗或變成狀況不良,系統就可以將其關機並重新啟動。
- 您可以使用更輕量的方法達成工作之間的協調。 例如,如果數個工作實例只需要對共用資源的協調存取,更好的解決方案就是使用開放式或悲觀鎖定來控制存取。
- 第三方解決方案,例如 Apache Zookeeper 可能是更有效率的解決方案。
工作負載設計
架構設計人員應該評估領導者選舉模式如何用於其工作負載的設計,以解決 Azure 良好架構架構支柱中涵蓋的目標和原則。 例如:
要素 | 此模式如何支援支柱目標 |
---|---|
可靠性設計決策可協助工作負載復原到故障,並確保它會在發生失敗后復原到完全正常運作的狀態。 | 此模式可藉由可靠地重新導向工作來減輕節點故障的影響。 當領導者故障時,它也會透過共識演算法實作故障轉移。 - RE:05 備援 - RE:07 自我修復 |
如同任何設計決策,請考慮對其他可能以此模式導入之目標的任何取捨。
範例
GitHub 上的領導者選舉範例示範如何在 Azure 儲存體 Blob 上使用租用,以提供實作共用分散式 Mutex 的機制。 此 Mutex 可用來在可用的背景工作實例群組中選取領導者。 取得租用的第一個實例會當選為領導者,並維持領導者,直到發行租用或無法續約為止。 如果領導者已無法使用,其他背景工作實例可以繼續監視 Blob 租用。
Blob 租用是 Blob 的獨佔寫入鎖定。 單一 Blob 在任何時間點只能是一個租用的主旨。 背景工作實例可以透過指定的 Blob 要求租用,如果其他背景工作實例沒有透過相同 Blob 保留租用,則會授與租用。 否則,要求會擲回例外狀況。
若要避免發生錯誤的領導者實例無限期地保留租用,請指定租用的存留期。 當此到期時,租用就會變成可用。 不過,當實例保留租用時,它可以要求更新租用,而且會再授與租用一段時間。 如果領導者實例想要保留租用,則可以持續重複此程式。 如需如何租用 Blob 的詳細資訊,請參閱租用 Blob(REST API)。
BlobDistributedMutex
下列 C# 範例中的 類別包含 RunTaskWhenMutexAcquired
方法,可讓背景工作實例嘗試透過指定的 Blob 取得租用。 建立物件時BlobDistributedMutex
,Blob 的詳細數據(名稱、容器和記憶體帳戶)會傳遞至 物件中的BlobSettings
建構函式(此對像是範例程式代碼中包含的簡單結構)。 建構函式也會接受 Task
,參考背景工作實例在成功透過 Blob 取得租用並選取領導者時,應該執行的程式碼。 請注意,處理取得租用的低階詳細數據的程式代碼會在名為 BlobLeaseManager
的個別協助程式類別中實作。
public class BlobDistributedMutex
{
...
private readonly BlobSettings blobSettings;
private readonly Func<CancellationToken, Task> taskToRunWhenLeaseAcquired;
...
public BlobDistributedMutex(BlobSettings blobSettings,
Func<CancellationToken, Task> taskToRunWhenLeaseAcquired, ... )
{
this.blobSettings = blobSettings;
this.taskToRunWhenLeaseAcquired = taskToRunWhenLeaseAcquired;
...
}
public async Task RunTaskWhenMutexAcquired(CancellationToken token)
{
var leaseManager = new BlobLeaseManager(blobSettings);
await this.RunTaskWhenBlobLeaseAcquired(leaseManager, token);
}
...
RunTaskWhenMutexAcquired
上述程式代碼範例中的 方法會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.TryAcquireLeaseOrWait(leaseManager, token);
if (!string.IsNullOrEmpty(leaseId))
{
// Create a new linked cancellation token source so that if either the
// original token is canceled or the lease can't be renewed, the
// leader task can be canceled.
using (var leaseCts =
CancellationTokenSource.CreateLinkedTokenSource(new[] { token }))
{
// Run the leader task.
var leaderTask = this.taskToRunWhenLeaseAcquired.Invoke(leaseCts.Token);
...
}
}
}
...
}
領導者啟動的工作也會以異步方式執行。 當這項工作正在執行時, RunTaskWhenBlobLeaseAcquired
下列程式代碼範例中顯示的方法會定期嘗試更新租用。 這有助於確保背景工作實例仍然是領導者。 在範例解決方案中,更新要求之間的延遲小於租用期間指定的時間,以防止另一個背景工作實例當選領導者。 如果更新因任何原因而失敗,則會取消領導者特定的工作。
如果租用無法更新或工作取消(可能是因為背景工作實例關閉所致),則會釋出租用。 此時,這個或另一個背景工作實例可以選取為領導者。 下列程式代碼擷取會顯示程序的這個部分。
private async Task RunTaskWhenBlobLeaseAcquired(
BlobLeaseManager leaseManager, CancellationToken token)
{
while (...)
{
...
if (...)
{
...
using (var leaseCts = ...)
{
...
// Keep renewing the lease in regular intervals.
// If the lease can't 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
// couldn't renew the lease) then cancel the other task.
await CancelAllWhenAnyCompletes(leaderTask, renewLeaseTask, leaseCts);
}
}
}
}
...
}
方法 KeepRenewingLease
是另一個使用 BlobLeaseManager
物件來更新租用的協助程式方法。 方法 CancelAllWhenAnyCompletes
會取消指定為前兩個參數的工作。 下圖說明如何使用 BlobDistributedMutex
類別來選取領導者,並執行協調作業的工作。
下列程式代碼範例示範如何在 BlobDistributedMutex
背景工作實例中使用 類別。 此程式代碼會透過租用容器中名為 MyLeaderCoordinatorTask
的 Blob 取得租用,Azure Blob 儲存體,並指定在方法中MyLeaderCoordinatorTask
定義的程式碼應該在選取領導者時執行。
// Create a BlobSettings object with the connection string or managed identity and the name of the blob to use for the lease
BlobSettings blobSettings = new BlobSettings(storageConnStr, "leases", "MyLeaderCoordinatorTask");
// Create a new BlobDistributedMutex object with the BlobSettings object and a task to run when the lease is acquired
var distributedMutex = new BlobDistributedMutex(
blobSettings, MyLeaderCoordinatorTask);
// Wait for completion of the DistributedMutex and the UI task before exiting
await distributedMutex.RunTaskWhenMutexAcquired(cancellationToken);
...
// Method that runs if the worker instance is elected the leader
private static async Task MyLeaderCoordinatorTask(CancellationToken token)
{
...
}
請注意下列關於範例解決方案的幾點:
- Blob 是潛在的單一失敗點。 如果 Blob 服務變成無法使用或無法存取,領導者將無法更新租用,而其他背景工作實例將無法取得租用。 在此情況下,任何背景工作實例都無法作為領導者。 不過,Blob 服務的設計目的是要復原,因此 Blob 服務的完整失敗會被視為極不可能。
- 如果領導者所執行的工作停滯不前,領導者可能會繼續更新租用,防止任何其他背景工作實例取得租用,並接管領導者職位,以協調工作。 在真實世界中,領導者的健康情況應該定期檢查。
- 選舉過程不具決定性。 您無法對哪些背景工作實例取得 Blob 租用並成為領導者進行任何假設。
- 做為 Blob 租用目標的 Blob 不應該用於任何其他用途。 如果背景工作實例嘗試將此 Blob 中儲存數據,除非背景工作角色實例是領導者且保存 Blob 租用,否則將無法存取此數據。
下一步
實作此模式時,下列指引也可能相關:
- 此模式具有可 下載的範例應用程式。
- 自動調整指引。 當應用程式負載不同時,可以啟動和停止工作主機的實例。 自動調整有助於在尖峰處理期間維持輸送量和效能。
- 以 工作為基礎的異步模式。
- 說明欺淩演算法的範例。
- 說明環形演算法的範例。
- Apache ZooKeeper 的用戶端連結庫 Apache Curator 。
- MSDN 上的租用 Blob (REST API) 一文。