競爭取用者模式

Azure Functions
Azure 服務匯流排

讓多個並行取用者處理相同傳訊通道上收到的訊息。 透過多個並行取用者,系統可以同時處理多個訊息,以優化輸送量、改善延展性和可用性,以及平衡工作負載。

內容和問題

在雲端中執行的應用程式應該會處理大量的要求。 應用程式與其同步處理每個要求,不如同步處理每個要求,而是讓應用程式透過傳訊系統傳遞至另一個以異步方式處理這些要求的服務(取用者服務)。 此策略有助於確保應用程式中的商業規則不會遭到封鎖,同時處理要求。

由於許多原因,要求數目可能會隨著時間而有很大的差異。 來自多個租用戶的用戶活動或匯總要求突然增加可能會導致無法預測的工作負載。 在尖峰時段,系統可能需要每秒處理數百個要求,而在其他時候,數目可能非常小。 此外,處理這些要求所執行工作的性質可能是高度變數。 藉由使用取用者服務的單一實例,您可以讓該實例充斥要求。 或者,傳訊系統可能會由來自應用程式的訊息湧入來多載。 為了處理此變動的工作負載,系統可以執行多個取用者服務的實例。 不過,這些取用者必須協調,以確保每個訊息只會傳遞至單一取用者。 工作負載也需要在取用者之間進行負載平衡,以防止實例成為瓶頸。

解決方案

使用訊息佇列,實作應用程式與消費者服務執行個體之間的通訊管道。 應用程式會將要求以訊息形式張貼至佇列,而取用者服務實例會接收來自佇列的訊息並加以處理。 此方法可讓相同的取用者服務實例集區處理來自應用程式任何實例的訊息。 此圖說明如何使用消息佇列將工作散發至服務的實例。

使用消息佇列將工作散發至服務的實例

注意

雖然這些訊息有多個取用者,但這與發佈訂閱模式(pub/sub)不同。 使用競爭取用者方法時,每個訊息都會傳遞至單一取用者進行處理,而使用 Pub/Sub 方法, 所有 取用者都會傳遞 每個 訊息。

此解決方案有下列優點:

  • 它提供負載層級的系統,可處理應用程式實例所傳送要求量的各種變化。 佇列會作為應用程式實例與取用者服務實例之間的緩衝區。 此緩衝區可協助將應用程式和服務實例的可用性和回應性影響降到最低。 如需詳細資訊,請參閱 佇列型負載撫平模式。 處理需要某些長時間執行的處理訊息並不會防止其他取用者服務實例同時處理其他訊息。

  • 其可改善可靠性。 如果生產者直接與取用者通訊,而不是使用此模式,但不會監視取用者,則如果取用者失敗,訊息可能會遺失或無法處理。 在此模式中,訊息不會傳送至特定服務實例。 失敗的服務實例不會封鎖產生者,而且訊息可由任何工作服務實例處理。

  • 它不需要取用者之間的複雜協調,也不需要生產者與取用者實例之間的複雜協調。 消息佇列可確保每個訊息至少傳遞一次。

  • 其可調整性。 當您套用 自動調整時,當訊息數量變動時,系統可以動態增加或減少取用者服務的實例數目。

  • 如果消息佇列提供交易式讀取作業,它可以改善復原能力。 如果取用者服務實例讀取並處理訊息做為交易作業的一部分,而且取用者服務實例失敗,此模式可確保訊息會傳回佇列,以便由另一個取用者服務的實例進行挑選和處理。 為了降低訊息持續失敗的風險,建議您使用 寄不出的信件佇列

問題和考慮

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

  • 訊息順序。 不保證取用者服務實例接收訊息的順序,而且不一定反映訊息建立的順序。 設計系統以確保訊息處理具有等冪性,因為這有助於消除訊息處理順序的任何相依性。 如需詳細資訊,請參閱 Jonathon Oliver部落格上的等冪模式

    Microsoft Azure 服務匯流排 佇列可以使用訊息會話來實作訊息的優先先出順序。 如需詳細資訊,請參閱 使用會話的傳訊模式。

  • 設計復原服務。 如果系統的設計目的是偵測並重新啟動失敗的服務實例,可能需要實作服務實例所執行的處理作為等冪作業,以將擷取和處理單一訊息的效果降到最低。

  • 偵測有害訊息。 格式不正確的訊息,或需要存取無法使用之資源的工作,可能會導致服務實例失敗。 系統應該防止這類訊息傳回佇列,並改為擷取並儲存這些訊息的詳細數據,以便在必要時進行分析。

  • 處理結果。 處理訊息的服務實例與產生訊息的應用程式邏輯完全分離,而且可能無法直接通訊。 如果服務實例產生必須傳回至應用程式邏輯的結果,這項信息必須儲存在兩者可存取的位置。 為了避免應用程式邏輯擷取不完整的數據,系統必須指出處理完成的時間。

    如果您使用 Azure,背景工作進程可以使用專用的訊息回復佇列,將結果傳回應用程式邏輯。 應用程式邏輯必須能夠將這些結果與原始訊息相互關聯。 異步傳訊入門更詳細地說明此案例。

  • 調整傳訊系統。 在大規模的解決方案中,單一消息佇列可能會因訊息數目而不知所措,並成為系統中的瓶頸。 在此情況下,請考慮分割傳訊系統,將訊息從特定產生者傳送至特定佇列,或使用負載平衡將訊息分散到多個消息佇列。

  • 確保傳訊系統的可靠性。 需要可靠的傳訊系統,才能保證應用程式加入佇列之後,將不會遺失訊息。 此系統對於確保所有訊息至少傳遞一次至關重要。

使用此模式的時機

當下列情況時,請使用此模式:

  • 應用程式的工作負載分成可以異步執行的工作。
  • 工作是獨立的,而且可以平行執行。
  • 工作量高度變動,需要可調整的解決方案。
  • 解決方案必須提供高可用性,而且如果工作處理失敗,則必須具有復原性。

當下列情況時,此模式可能不實用:

  • 將應用程式工作負載分成離散工作並不容易,或工作之間的高度相依性。
  • 工作必須以同步方式執行,而且應用程式邏輯必須等候工作完成再繼續。
  • 工作必須依特定順序執行。

某些傳訊系統支援會話,可讓產生者將訊息分組在一起,並確保它們全都由相同的取用者處理。 此機制可以搭配優先順序的訊息使用(如果支持的話),以實作一種訊息排序形式,以依序將訊息從產生者傳遞至單一取用者。

工作負載設計

架構設計人員應評估競爭取用者模式如何用於其工作負載的設計,以解決 Azure 架構架構支柱涵蓋的目標和原則。 例如:

要素 此模式如何支援支柱目標
可靠性設計決策可協助工作負載復原到故障,並確保它會在發生失敗后復原到完全正常運作的狀態。 此模式會將取用者視為複本來建置佇列處理中的備援,因此實例失敗不會防止其他取用者處理佇列訊息。

- RE:05 備援
- RE:07 背景工作
成本優化著重於維持和改善工作負載的投資報酬率。 此模式可協助您優化成本,方法是啟用以佇列深度為基礎的調整,在佇列是空的時減少為零。 它也可以藉由讓您限制並行取用者實例數目上限來優化成本。

- CO:05 速率優化
- CO:07 元件成本
效能效率 可透過調整、數據、程式代碼的優化,有效率地協助您的工作負載 符合需求 將所有取用者節點的負載分散會增加根據佇列深度的使用率和動態調整,將過度布建降至最低。

- PE:05 調整和分割
- PE:07 程式代碼和基礎結構

如同任何設計決策,請考慮對其他可能以此模式導入之目標的任何取捨。

範例

Azure 提供 服務匯流排 佇列和 Azure 函式佇列觸發程式,當結合時,會直接實作此雲端設計模式。 Azure Functions 會透過觸發程式和系結與 Azure 服務匯流排 整合。 與 服務匯流排 整合可讓您建置函式,以取用發行者傳送的佇列訊息。 發佈應用程式會將訊息張貼至佇列,而實作為 Azure Functions 的取用者可以從此佇列擷取訊息並加以處理。

為了恢復復原,服務匯流排 佇列可讓取用者在從佇列擷取訊息時使用PeekLock模式;此模式實際上不會移除訊息,而只會隱藏其他取用者。 Azure Functions 運行時間會在 PeekLock 模式中收到訊息,如果函式成功完成,它會在訊息上呼叫 Complete,或者如果函式失敗,它可能會呼叫 Abandon,而且訊息將會再次顯示,讓另一個取用者擷取它。 如果函式執行的期間超過 PeekLock 逾時,只要函式正在執行,就會自動更新鎖定。

Azure Functions 可以根據佇列的深度相應放大/縮小,而全都作為佇列的競爭取用者。 如果建立函式的多個實例,它們全都會透過獨立提取和處理訊息來競爭。

如需使用 Azure 服務匯流排 佇列的詳細資訊,請參閱 服務匯流排 佇列、主題和訂用帳戶

如需佇列觸發 Azure Functions 的資訊,請參閱 Azure Functions 的 Azure 服務匯流排 觸發程式。

下列程式代碼示範如何使用 實例建立新的訊息,並將其傳送至 服務匯流排 佇列ServiceBusClient

private string serviceBusConnectionString = ...;
...

  public async Task SendMessagesAsync(CancellationToken  ct)
  {
   try
   {
    var msgNumber = 0;

    var serviceBusClient = new ServiceBusClient(serviceBusConnectionString);

    // create the sender
    ServiceBusSender sender = serviceBusClient.CreateSender("myqueue");

    while (!ct.IsCancellationRequested)
    {
     // Create a new message to send to the queue
     string messageBody = $"Message {msgNumber}";
     var message = new ServiceBusMessage(messageBody);

     // Write the body of the message to the console
     this._logger.LogInformation($"Sending message: {messageBody}");

     // Send the message to the queue
     await sender.SendMessageAsync(message);

     this._logger.LogInformation("Message successfully sent.");
     msgNumber++;
    }
   }
   catch (Exception exception)
   {
    this._logger.LogException(exception.Message);
   }
  }

下列程式代碼範例示範以 C# Azure 函式撰寫的取用者,可讀取訊息元數據並記錄 服務匯流排 佇列訊息。 請注意屬性ServiceBusTrigger如何用來將它系結至 服務匯流排 佇列。

[FunctionName("ProcessQueueMessage")]
public static void Run(
    [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")]
    string myQueueItem,
    Int32 deliveryCount,
    DateTime enqueuedTimeUtc,
    string messageId,
    ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function consumed message: {myQueueItem}");
    log.LogInformation($"EnqueuedTimeUtc={enqueuedTimeUtc}");
    log.LogInformation($"DeliveryCount={deliveryCount}");
    log.LogInformation($"MessageId={messageId}");
}

下一步

  • 異步傳訊入門。 消息佇列是異步通訊機制。 如果取用者服務需要將回復傳送至應用程式,可能需要實作某種形式的回應傳訊。 異步傳訊入門提供如何使用消息佇列實作要求/回復傳訊的資訊。

  • 自動調整指引。 可能會啟動和停止取用者服務的實例,因為佇列應用程式張貼訊息的長度會有所不同。 自動調整有助於在尖峰處理期間維持輸送量。

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

  • 計算資源匯總模式。 您可以將取用者服務的多個實例合併成單一程式,以降低成本和管理額外負荷。 計算資源匯總模式描述遵循此方法的優點和取捨。

  • 佇列型負載撫平模式。 引進消息佇列可將復原功能新增至系統,讓服務實例處理來自應用程式實例的要求量廣泛。 消息佇列會做為緩衝區,以將負載層級設定為層級。 佇列型負載撫平模式會更詳細地描述此案例。