竞争消费者模式

【博文目录>>>】


竞争消费者模式

使多个并发使用者能够处理在同一消息通道上接收的消息。这种模式使系统能够同时处理多条消息,以优化吞吐量,提高可伸缩性和可用性,并平衡工作负载。

背景与问题

运行在云中的应用程序可能会处理大量请求。与其同步处理每个请求,不如让应用程序通过消息系统将它们传递给另一个服务(消费者(服务)异步处理它们的服务。此策略有助于确保在处理请求时应用程序中的业务逻辑不会被阻塞。

由于许多原因,请求的数量可能会随着时间的推移而有很大差异。来自多个租户的用户活动或聚合请求的突然爆发可能会导致不可预测的工作负载。在高峰时间,系统可能需要每秒处理数百个请求,而在其他时候,处理请求的数量可能非常少。此外,处理这些请求所执行的工作的性质可能是高度可变的。使用单实例消费者服务可能会导致该实例被请求淹没,也会引起消息系统被来自应用程序的大量消息过载。为了处理这种起伏不定的工作负载,系统可以运行多实例消费者服务的。但是,必须协调这些消费者,以确保每条消息只传递给单消费用者。工作负载还需要跨消费者进行负载平衡,以防止实例成为瓶颈。

解决方案

使用消息队列实现应用程序和消费者服务实例之间进行通信。应用程序以消息的形式向队列发送请求,消费者服务实例接收来自队列的消息并对其进行处理。此方法允许相同的使用者服务实例池处理来自应用程序的任何实例的消息。图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

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页