Editar

Padrão de Consumidores Concorrentes

Azure Functions
Azure Service Bus

Permita que vários consumidores em simultâneo processem mensagens recebidas no mesmo canal de mensagens. Com vários consumidores simultâneos, um sistema pode processar várias mensagens simultaneamente para otimizar a taxa de transferência, melhorar a escalabilidade e a disponibilidade e equilibrar a carga de trabalho.

Contexto e problema

Espera-se que uma aplicação em execução na nuvem processe um grande número de pedidos. Em vez de processar cada pedido de forma síncrona, uma técnica comum passa por colocar a aplicação a transmiti-los através de um sistema de mensagens para outro serviço (um serviço de consumidor) que os processa de forma assíncrona. Essa estratégia ajuda a garantir que a lógica de negócios no aplicativo não seja bloqueada enquanto as solicitações estão sendo processadas.

O número de pedidos pode variar significativamente ao longo do tempo por diversos motivos. Um aumento repentino da atividade do utilizador ou dos pedidos agregados provenientes de vários inquilinos pode produzir uma carga de trabalho imprevisível. Nas horas de ponta, um sistema pode ter de processar muitas centenas de pedidos por segundo, enquanto noutras alturas o número pode ser muito pequeno. Além disso, a natureza do trabalho realizado para processar estes pedidos pode ser altamente variável. Usando uma única instância do serviço ao consumidor, você pode fazer com que essa instância seja inundada de solicitações. Ou, o sistema de mensagens pode estar sobrecarregado por um influxo de mensagens que vêm do aplicativo. Para processar esta carga de trabalho flutuante, o sistema pode executar várias instâncias do serviço de consumidor. No entanto, estes consumidores têm de estar coordenados para garantir que cada mensagem é apenas enviada para um único consumidor. A carga de trabalho também tem de ser balanceada entre os consumidores para impedir que uma instância se torne num estrangulamento.

Solução

Utilize uma fila de mensagens para implementar o canal de comunicação entre a aplicação e as instâncias do serviço de consumidor. A aplicação publica pedidos sob a forma de mensagens na fila. Por sua vez, as instâncias do serviço de consumidor recebem as mensagens da fila e processam-nas. Esta abordagem permite que o mesmo conjunto de instâncias do serviço de consumidor processe mensagens de qualquer instância da aplicação. A figura mostra a utilização de uma fila de mensagens para distribuir o trabalho para as instâncias de um serviço.

Utilização de uma fila de mensagens para distribuir trabalho para instâncias de um serviço

Nota

Embora existam vários consumidores dessas mensagens, isso não é o mesmo que o padrão Publicar assinatura (pub/sub). Com a abordagem de Consumidores Concorrentes, cada mensagem é passada a um único consumidor para processamento, enquanto com a abordagem Pub/Sub, todos os consumidores recebem todas as mensagens.

Esta solução possui os benefícios seguintes:

  • Fornece um sistema de carga redistribuída que pode processar grandes variações no volume de pedidos enviados pelas instâncias da aplicação. A fila funciona como uma memória intermédia entre as instâncias da aplicação e as instâncias do serviço de consumidor. Esse buffer pode ajudar a minimizar o impacto na disponibilidade e na capacidade de resposta, tanto para o aplicativo quanto para as instâncias de serviço. Para obter mais informações, consulte Padrão de nivelamento de carga baseado em fila. O processamento de uma mensagem que precisa de um processamento de execução longa não impede que as outras mensagens sejam processadas em simultâneo por outras instâncias do serviço de consumidor.

  • Melhora a fiabilidade. Se um produtor comunicar diretamente com um consumidor em vez de utilizar este padrão, mas não monitorizar o consumidor, existirá uma grande probabilidade de as mensagens se perderem ou não serem processadas se o consumidor falhar. Neste padrão, as mensagens não são enviadas para uma instância de serviço específica. Uma instância de serviço que falha não bloqueia um produtor e as mensagens podem ser processadas por qualquer instância de serviço em funcionamento.

  • Não requer uma coordenação complexa entre os consumidores ou entre o produtor e as instâncias do consumidor. A fila de mensagens assegura que cada mensagem é entregue pelo menos uma vez.

  • É escalável. Quando você aplica o dimensionamento automático, o sistema pode aumentar ou diminuir dinamicamente o número de instâncias do serviço ao consumidor à medida que o volume de mensagens flutua.

  • Poderá melhorar a resiliência se a fila de mensagens fornecer operações de leitura transacionais. Se uma instância do serviço de consumidor ler e processar a mensagem como parte de uma operação transacional e se a instância do serviço de consumidor falhar, este padrão poderá garantir que a mensagem será devolvida à fila para que seja recolhida e processada por outra instância do serviço de consumidor. Para reduzir o risco de uma mensagem falhar continuamente, recomendamos que você faça uso de filas de mensagens mortas.

Problemas e considerações

Na altura de decidir como implementar este padrão, considere os seguintes pontos:

  • Ordenação de mensagens. A ordem pela qual as instâncias do serviço de consumidor recebem as mensagens não é garantida nem reflete necessariamente a ordem pela qual as mensagens foram criadas. Crie o sistema para garantir que o processamento das mensagens é idempotente. Eliminará assim qualquer dependência relativamente à ordem de processamento das mensagens. Para obter mais informações, consulte Padrões de idempotência no blog de Jonathon Oliver.

    As Filas do Microsoft Azure Service Bus podem implementar uma ordenação de mensagens first-in-first-out garantida com sessões de mensagens. Para obter mais informações, veja Messaging Patterns Using Sessions (Padrões de Mensagens com Sessões).

  • Criar serviços para fins de resiliência. Se o sistema tiver sido concebido para detetar e reiniciar instâncias de serviço que falham, poderá ser necessário implementar o processamento realizado pelas instâncias do serviço como operações idempotentes para minimizar os efeitos de uma única mensagem a ser obtida e processada mais do que uma vez.

  • Detetar mensagens não processáveis. Uma mensagem incorretamente formada ou uma tarefa que requer acesso a recursos indisponíveis pode fazer com que uma instância de serviço falhe. O sistema deve evitar que estas mensagens sejam devolvidas à fila. Em vez disso, deve capturar e armazenar os detalhes destas mensagens noutro local para que possam ser analisadas, se necessário.

  • Processar resultados. A instância de serviço a processar uma mensagem está totalmente desacoplada da lógica aplicacional que gera a mensagem e pode não ter a capacidade de comunicar diretamente. Se a instância de serviço gerar resultados que têm de ser transmitidos para a lógica aplicacional, estas informações terão de ser armazenadas numa localização que esteja acessível para ambos. Para evitar que a lógica aplicacional obtenha dados incompletos, o sistema tem de indicar quando é que o processamento é concluído.

    Se estiver a utilizar o Azure, um processo de trabalho poderá transmitir os resultados de volta para a lógica aplicacional com uma fila de respostas de mensagens dedicada. A lógica aplicacional tem de conseguir correlacionar estes resultados com a mensagem original. Este cenário é descrito com maior detalhe no Asynchronous Messaging Primer (Manual Básico de Mensagens Assíncronas).

  • Dimensionar o sistema de mensagens. Numa solução em grande escala, uma fila de mensagens única pode ficar sobrecarregada pelo número de mensagens e tornar-se num estrangulamento no sistema. Nesta situação, considere a criação de partições do sistema de mensagens para enviar mensagens de produtores específicos a uma fila específica ou utilize o balanceamento de carga para distribuir as mensagens por várias filas de mensagens.

  • Garantir a fiabilidade do sistema de mensagens. É necessário um sistema de mensagens fiável para assegurar que, após a aplicação colocar uma mensagem em fila, esta não é perdida. Este sistema é essencial para garantir que todas as mensagens são entregues pelo menos uma vez.

Quando utilizar este padrão

Utilize este padrão quando:

  • A carga de trabalho de uma aplicação está dividida em tarefas que podem ser executadas de forma assíncrona.
  • As tarefas são independentes e podem ser executadas em paralelo.
  • O volume de trabalho é altamente variável e requer uma solução dimensionável.
  • A solução tem de fornecer uma elevada disponibilidade e tem de ser resiliente caso ocorra uma falha no processamento de uma tarefa.

Este padrão poderá não ser prático quando:

  • Não é fácil separar a carga de trabalho da aplicação em tarefas discretas ou há um elevado grau de dependência entre as tarefas.
  • As tarefas devem ser executadas de forma síncrona e a lógica aplicacional tem de aguardar pela conclusão de uma tarefa antes de continuar.
  • As tarefas têm de ser executadas numa sequência específica.

Alguns sistemas de mensagens suportam sessões que permitem a um produtor agrupar mensagens e asseguram que estas são processadas pelo mesmo consumidor. Este mecanismo pode ser utilizado com mensagens prioritárias (se suportadas) para implementar uma forma de ordenação de mensagens em sequência de um produtor para um único consumidor.

Design da carga de trabalho

Um arquiteto deve avaliar como o padrão Consumidores Concorrentes pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:

Pilar Como esse padrão suporta os objetivos do pilar
As decisões de projeto de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. Esse padrão cria redundância no processamento de filas ao tratar os consumidores como réplicas, portanto, uma falha de instância não impede que outros consumidores processem mensagens da fila.

- RE:05 Redundância
- RE:07 Trabalhos em segundo plano
A Otimização de Custos está focada em sustentar e melhorar o retorno do investimento da sua carga de trabalho. Esse padrão pode ajudá-lo a otimizar os custos, permitindo o dimensionamento baseado na profundidade da fila, até zero quando a fila está vazia. Ele também pode otimizar custos, permitindo que você limite o número máximo de instâncias de consumidores simultâneas.

- CO:05 Otimização de taxa
- CO:07 Custos dos componentes
A Eficiência de Desempenho ajuda sua carga de trabalho a atender às demandas de forma eficiente por meio de otimizações em escala, dados e código. A distribuição de carga em todos os nós de consumo aumenta a utilização e o dimensionamento dinâmico com base na profundidade da fila, minimizando o provisionamento excessivo.

- PE:05 Dimensionamento e particionamento
- PE:07 Código e infraestrutura

Como em qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com esse padrão.

Exemplo

O Azure fornece Filas do Barramento de Serviço e gatilhos de fila da Função do Azure que, quando combinados, são uma implementação direta desse padrão de design de nuvem. O Azure Functions integra-se com o Barramento de Serviço do Azure por meio de gatilhos e associações. A integração com o Service Bus permite criar funções que consomem mensagens de fila enviadas por editores. O(s) aplicativo(s) de publicação postará(ão) mensagens em uma fila e os consumidores, implementados como Azure Functions, poderão recuperar mensagens dessa fila e manipulá-las.

Para resiliência, uma fila do Service Bus permite que um consumidor use PeekLock o modo quando recupera uma mensagem da fila, esse modo não remove a mensagem, mas simplesmente a oculta de outros consumidores. O tempo de execução do Azure Functions recebe uma mensagem no modo PeekLock, se a função for concluída com êxito, ela chama Complete na mensagem, ou pode chamar Abandon se a função falhar e a mensagem ficará visível novamente, permitindo que outro consumidor a recupere. Se a função for executada por um período maior do que o tempo limite do PeekLock, o bloqueio será renovado automaticamente enquanto a função estiver em execução.

O Azure Functions pode expandir/aumentar com base na profundidade da fila, todos atuando como consumidores concorrentes da fila. Se várias instâncias das funções forem criadas, todas elas competem puxando e processando as mensagens de forma independente.

Para obter informações detalhadas sobre a utilização das filas do Azure Service Bus, veja Filas, tópicos e subscrições do Service Bus.

Para obter informações sobre o Azure Functions acionado por fila, consulte Gatilho do Barramento de Serviço do Azure para Azure Functions.

O código a seguir mostra como você pode criar uma nova mensagem e enviá-la para uma fila do Service Bus usando uma ServiceBusClient instância.

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);
   }
  }

O exemplo de código a seguir mostra um consumidor, escrito como uma Função do Azure em C#, que lê metadados de mensagem e registra uma mensagem de fila do Service Bus. Observe como o ServiceBusTrigger atributo é usado para vinculá-lo a uma fila do Service Bus.

[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}");
}

Próximos passos

  • Asynchronous Messaging Primer (Manual Básico de Mensagens Assíncronas). As Filas de mensagens são um mecanismo de comunicação assíncrono. Se um serviço de consumidor precisar de enviar uma resposta para uma aplicação, poderá ser necessário implementar alguma forma de mensagem de resposta. O Manual Básico de Mensagens Assíncronas fornece informações sobre como implementar mensagens de pedido/resposta com filas de mensagens.

  • Orientações de Dimensionamento Automático. Pode ser possível iniciar e parar instâncias de um serviço de consumidor, uma vez que o comprimento da fila na qual as aplicações publicam mensagens varia. O dimensionamento automático pode ajudar a manter o débito durante os períodos de pico de processamento.

Os padrões e as orientações que se seguem podem ser relevantes ao implementar este padrão:

  • Padrão de Consolidação de Recursos de Computação. Pode ser possível consolidar várias instâncias de um serviço de consumidor num único processo para reduzir os gastos e os custos de gestão. O padrão de Consolidação de Recursos de Computação descreve os benefícios e os compromissos desta abordagem.

  • Padrão de Nivelamento de Carga Baseado na Fila. A introdução de uma fila de mensagens pode adicionar resiliência ao sistema, o que permite que as instâncias de serviço processem diversos e variados volumes de pedidos de instâncias da aplicação. A fila de mensagens funciona como uma memória intermédia que redistribui a carga. O padrão de Redistribuição de Carga Baseada na Fila descreve este cenário mais detalhadamente.