競爭消費者模式

【博文目錄>>>】


競爭消費者模式

使多個併發使用者能夠處理在同一消息通道上接收的消息。這種模式使系統能夠同時處理多條消息,以優化吞吐量,提高可伸縮性和可用性,並平衡工作負載。

背景與問題

運行在雲中的應用程序可能會處理大量請求。與其同步處理每個請求,不如讓應用程序通過消息系統將它們傳遞給另一個服務(消費者(服務)異步處理它們的服務。此策略有助於確保在處理請求時應用程序中的業務邏輯不會被阻塞。

由於許多原因,請求的數量可能會隨着時間的推移而有很大差異。來自多個租戶的用戶活動或聚合請求的突然爆發可能會導致不可預測的工作負載。在高峯時間,系統可能需要每秒處理數百個請求,而在其他時候,處理請求的數量可能非常少。此外,處理這些請求所執行的工作的性質可能是高度可變的。使用單實例消費者服務可能會導致該實例被請求淹沒,也會引起消息系統被來自應用程序的大量消息過載。爲了處理這種起伏不定的工作負載,系統可以運行多實例消費者服務的。但是,必須協調這些消費者,以確保每條消息只傳遞給單消費用者。工作負載還需要跨消費者進行負載平衡,以防止實例成爲瓶頸。

解決方案

使用消息隊列實現應用程序和消費者服務實例之間進行通信。應用程序以消息的形式向隊列發送請求,消費者服務實例接收來自隊列的消息並對其進行處理。此方法允許相同的使用者服務實例池處理來自應用程序的任何實例的消息。圖1演示了這個體系結構。
在這裏插入圖片描述

圖1-使用消息隊列將工作分發給服務實例

此解決方案具有以下優點:

  • 它啓用了一個固有的負載均衡系統,該系統可以處理應用程序實例發送的請求數量的各種變化。隊列充當應用程序實例和使用者服務實例之間的緩衝區,這有助於將對應用程序實例和服務實例的可用性和響應性的影響降到最低(如基於隊列的負載均衡模式)。處理需要長時間執行的消息,並且不會影響其他消息在其他消費上的併發執行。
  • 它提高了可靠性。如果生產者直接與消費者通信而不是使用此模式,但不監視消費者,那麼如果消費者失敗,則很有可能會丟失消息或無法處理消息。在這種模式下,消息不會發送到特定的服務實例,失敗的服務實例不會阻止生產者,並且任何可用的服務實例都可以處理消息。
  • 它不需要消費者之間或生產者與消費者之間的複雜協調。消息隊列確保每個消息至少傳遞一次。
  • 它是可伸縮的。當消息數量波動時,系統可以動態地增加或減少使用者服務的實例數。
  • 如果消息隊列提供事務性讀取操作,則可以提高彈性。如果消費者服務實例作爲事務操作的一部分讀取和處理消息,並且如果該消費者服務實例隨後失敗,則此模式可以確保消息將返回到隊列,由消費者服務的另一個實例獲取和處理。

問題和思考

在決定如何實現此模式時,請考慮以下幾點:

  • 消息排序。消費者服務實例接收消息的順序不能得到保證,並且不一定反映創建消息的順序。設計系統以確保消息處理是冪等的,因爲這將有助於消除對消息處理順序的任何依賴性。有關冪等的更多信息,請參閱Jonathon Oliver博客上的冪等模式


Microsoft Azure服務總線隊列可以通過使用消息會話來實現保證的消息的先進先出順序。有關更多信息,請參見MSDN上的使用會話的消息傳遞模式在MSDN上。

  • 設計彈性服務。如果將系統設計爲檢測並重新啓動失敗的服務實例,則可能有必要將服務實例執行的處理作爲冪等操作來實現,以最大程度地減少單個消息被多次檢索和處理的影響。

  • 檢測毒物消息。格式錯誤的消息或需要訪問不可用資源的任務可能會導致服務實例失敗。系統應防止此類消息返回到隊列,而應在其他位置捕獲和存儲這些消息的詳細信息,以便在必要時進行分析。

  • 處理結果。處理消息的服務實例與生成消息的應用程序邏輯完全脫鉤,並且它們可能無法直接通信。如果服務實例生成的結果必須傳遞迴應用程序邏輯,則此信息必須存儲在雙方都可訪問的位置,並且系統必須提供一些指示,指示處理何時完成,以防止應用程序邏輯檢索不完整的數據。


如果使用的是Azure,則輔助進程可以使用專用的消息答覆隊列將結果傳遞迴應用程序邏輯。應用程序邏輯必須能夠將這些結果與原始消息相關聯。異步消息入門中更詳細地描述了這種情況。

  • 擴展消息系統。在大規模解決方案中,單個消息隊列可能會被消息數量淹沒,併成爲系統中的瓶頸。在這種情況下,請考慮對消息傳遞系統進行分區以將消息從特定生產者定向到特定隊列,或者使用負載平衡在多個消息隊列之間分配消息。

  • 確保消息系統的可靠性。需要一個可靠的消息系統來確保一旦應用程序加入消息後,消息就不會丟失。這對於確保所有消息至少傳遞一次至關重要。

何時使用此模式

在以下情況下使用此模式:

  • 應用程序的工作負載分爲可以異步運行的任務。
  • 任務是獨立的,可以並行運行。
  • 工作量變化很大,需要可擴展的解決方案。
  • 該解決方案必須提供高可用性,並且在任務處理失敗時必須具有彈性。

在以下情況下,此模式可能不適合:

  • 將應用程序工作負載劃分爲離散的任務並不容易,或者任務之間存在高度依賴性。
  • 任務必須同步執行,並且應用程序邏輯必須等待任務完成才能繼續。
  • 必須按特定順序執行任務。


一些消息系統支持會話,使生產者能夠將消息組合在一起,並確保所有消息都由同一個使用者處理。這種機制可以與優先級消息(如果支持的話)一起使用,以實現一種消息排序形式,將消息按順序從生產者傳遞到單個使用者。

示例

Azure提供了存儲隊列和服務總線隊列,可以用作實現此模式的合適機制。應用程序邏輯可以將消息發佈到隊列中,並且以一個或多個角色的任務形式實現的消費者可以從此隊列中檢索消息並進行處理。爲了具有彈性,服務總線隊列使使用者在從隊列中檢索消息時可以使用PeekLock模式。此模式實際上不會刪除消息,而只是將其對其他消費者隱藏。原始使用者可以在完成處理後將其刪除。如果使用者失敗,則PeekLock將超時並且該消息將再次變爲可見,從而允許其他使用者檢索它。


有關使用Azure Service Bus隊列的詳細信息,請參閱MSDN上的服務總線隊列、主題和訂閱在MSDN上。有關使用Azure存儲隊列的信息,請參閱在MSDN上的如何使用隊列存儲服務

以下代碼顯示了CompetingConsumers解決方案中QueueManager類的示例,該示例可供下載以供本指南使用,該示例演示瞭如何在Web角色或輔助角色中使用Start事件處理程序中的QueueClient實例來創建隊列。

private string queueName = ...;
private string connectionString = ...;
...

public async Task Start(){
  // Check if the queue already exists.
  var manager = NamespaceManager.CreateFromConnectionString(this.connectionString);
  if (!manager.QueueExists(this.queueName)){
    var queueDescription = new QueueDescription(this.queueName);

    // Set the maximum delivery count for messages in the queue. A message 
    // is automatically dead-lettered after this number of deliveries. The
    // default value for dead letter count is 10.
    queueDescription.MaxDeliveryCount = 3;

    await manager.CreateQueueAsync(queueDescription);
  }
  ...

  // Create the queue client. By default the PeekLock method is used.
  this.client = QueueClient.CreateFromConnectionString(this.connectionString, this.queueName);
}

下一個代碼片段顯示了應用程序如何創建並向隊列發送一批消息。

public async Task SendMessagesAsync(){
  // Simulate sending a batch of messages to the queue.
  var messages = new List<BrokeredMessage>();

  for (int i = 0; i < 10; i++){
    var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
    messages.Add(message);
  }
  await this.client.SendBatchAsync(messages);
}

以下代碼顯示了消費者服務實例如何通過遵循事件驅動的方法從隊列接收消息。 ReceiveMessages方法的processMessageTask參數是一個委託,該委託引用要在收到消息時運行的代碼。 此代碼異步運行。

private ManualResetEvent pauseProcessingEvent;
...

public void ReceiveMessages(Func<BrokeredMessage, Task> processMessageTask){
  // Set up the options for the message pump.
  var options = new OnMessageOptions();

  // When AutoComplete is disabled it is necessary to manually  
  // complete or abandon the messages and handle any errors.
  options.AutoComplete = false;
  options.MaxConcurrentCalls = 10;
  options.ExceptionReceived += this.OptionsOnExceptionReceived;
  // Use of the Service Bus OnMessage message pump.   
  // The OnMessage method must be called once, otherwise an exception will occur.  
  this.client.OnMessageAsync(    async (msg) =>    {      
    // Will block the current thread if Stop is called.      
    this.pauseProcessingEvent.WaitOne();      
    // Execute processing task here.      
    await processMessageTask(msg);   
  },    
  options);
}
...

private void OptionsOnExceptionReceived(object sender,  
    ExceptionReceivedEventArgs exceptionReceivedEventArgs){...}

請注意,隨着隊列長度的變化,自動縮放功能(例如Azure中可用的功能)可用於啓動和停止角色實例。有關更多信息,請參見自動縮放指導。此外,不必在角色實例與工作進程之間保持一一對應的關係,單個角色實例可以實現多個工作進程。有關更多信息,請參閱計算資源整合模式.

相關模式和指導

實施此模式時,以下模式和指導可能是相關的:

  • 異步消息入門。消息隊列本質上是一種異步通信機制。如果消費者服務需要將答覆發送給應用程序,則可能有必要實施某種形式的響應消息傳遞。異步消息入門提供了有關如何通過使用消息隊列來實現請求/答覆消息傳遞的信息。
  • 自動縮放指導。當應用程序發佈消息的隊列長度發生變化時,可以啓動和停止消費者服務實例。自動縮放有助於在高峯處理期間維持吞吐量。。
  • 計算資源整合模式。可以將消費者服務的多個實例合併爲一個流程,以降低成本和管理開銷。計算資源整合模式描述了採用這種方法的好處和折衷方案。
  • 基於隊列的負載均衡模式。引入消息隊列可以增加系統的彈性,從而使服務實例能夠處理來自應用程序實例的多種請求。消息隊列有效地充當了一個緩衝區,該緩衝區對負載進行分級。基於隊列的負載均衡模式更詳細地描述了這種情況。

更多信息

此模式有一個與其相關聯的示例應用程序。您可以從微軟下載中心下載“雲設計模式-示例代碼:http://aka.ms/cloud-design-patterns-sample.

原文鏈接

https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn568101%28v%3dpandp.10%29

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章