領導者選舉模式

Azure Blob 儲存體

藉由選擇一個實例做為負責管理其他人的領導者,協調分散式應用程式中共同作業實例集合所執行的動作。 這有助於確保實例不會彼此衝突、造成共用資源的爭用,或不小心干擾其他實例正在執行的工作。

內容和問題

典型的雲端應用程式有許多工作會以協調方式運作。 這些工作全都可以是執行相同程式碼並要求存取相同資源的實例,或者它們可能會平行運作,以執行複雜計算的個別部分。

工作實例可能會在大部分時間分別執行,但可能也需要協調每個實例的動作,以確保它們不會衝突、造成共用資源的爭用,或不小心干擾其他工作實例正在執行的工作。

例如:

  • 在實作水平調整的雲端式系統中,相同工作的多個實例可以同時執行,且每個實例都為不同的使用者提供服務。 如果這些實例寫入共用資源,就必須協調其動作,以防止每個實例覆寫其他人所做的變更。
  • 如果工作會以平行方式執行複雜計算的個別元素,則必須在結果全部完成時匯總。

工作實例都是同儕節點,因此沒有可作為協調器或匯總工具的自然領導者。

解決方案

應該選取單一工作實例作為領導者,而且此實例應該協調其他次級工作實例的動作。 如果所有工作實例都執行相同的程式代碼,則每個實例都能夠做為領導者。 因此,必須謹慎管理選舉程式,以防止兩個或多個實例同時接管領導人職位。

系統必須提供健全的機制來選取領導者。 此方法必須處理網路中斷或進程失敗等事件。 在許多解決方案中,次級工作實例會透過某種類型的活動訊號方法或輪詢來監視領導者。 如果指定的領導者意外終止,或網路失敗會使領導者無法供次級工作實例使用,則必須讓他們選擇新的領導者。

在分散式環境中選擇一組工作中的領導者有幾個策略,包括:

  • 選取具有最低排名實例或進程標識符的工作實例。
  • 競相取得共用分散式 Mutex。 取得 Mutex 的第一個工作實例是領導者。 不過,系統必須確定,如果領導者終止或與系統的其餘部分中斷連線,則會釋放 mutex,以允許另一個工作實例成為領導者。
  • 實作其中一種常見的領導者選舉演算法,例如 欺負演算法環形演算法。 這些演算法假設選舉中的每個候選人都有唯一的標識碼,而且它可以可靠地與其他候選人通訊。

問題和考慮

決定如何實作此模式時,請考慮下列幾點:

  • 選擇領導者的程式應該能夠復原暫時性和持續性失敗。
  • 必須能夠偵測領導者何時失敗或變成無法使用(例如通訊失敗)。 系統相依,需要多快的偵測。 某些系統可能會在沒有領導者的情況下短暫運作,在此期間可能會修正暫時性錯誤。 在其他情況下,可能需要立即偵測領導者失敗並觸發新的選舉。
  • 在實作水平自動調整的系統中,如果系統相應減少並關閉部分運算資源,則領導者可能會終止。
  • 使用共用分散式 Mutex 引進了對提供 Mutex 的外部服務相依性。 服務構成單一失敗點。 如果因為任何原因而無法使用,系統將無法選出領導者。
  • 以領導者身分使用單一專用程式是一種直接的方法。 不過,如果進程失敗,重新啟動時可能會有顯著的延遲。 如果導致延遲等待領導者協調作業,則所產生的延遲可能會影響其他進程的效能和回應時間。
  • 手動實作其中一個領導者選舉演算法,可提供微調和優化程序代碼的最大彈性。
  • 避免讓領導者成為系統中的瓶頸。 領導的目的是協調次級工作的工作,而且不一定必須參與這項工作本身,但如果任務不當選為領導,它應該能夠這樣做。

使用此模式的時機

當分散式應用程式中的工作,例如雲端裝載的解決方案,需要仔細協調,而且沒有自然領導者時,請使用此模式。

如果:

  • 有一個自然領導者或專用的程序,隨時可以做為領導者。 例如,您可以實作協調工作實例的單一進程。 如果此程式失敗或變成狀況不良,系統就可以將其關機並重新啟動。
  • 您可以使用更輕量的方法達成工作之間的協調。 例如,如果數個工作實例只需要對共用資源的協調存取,更好的解決方案就是使用開放式或悲觀鎖定來控制存取。
  • 第三方解決方案更合適。 例如,Microsoft Azure HDInsight 服務(以 Apache Hadoop 為基礎)會使用 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 類別來選取領導者,並執行協調作業的工作。

圖 1 說明 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 租用,否則將無法存取此數據。

下一步

實作此模式時,下列指引也可能相關: