Pular para o conteúdo principal

Competing Consumers

Descrição​

O padrão Competing Consumers permite que várias instâncias consumidoras processem, de forma concorrente, mensagens recebidas pelo mesmo canal de mensageria. Os produtores publicam solicitações como mensagens em uma fila, e um pool de consumidores retira e processa essas mensagens de forma assíncrona, sem que o produtor precise saber qual instância fará o trabalho.

Cada mensagem é entregue a apenas um consumidor: as instâncias "competem" pelas mensagens disponíveis. Isso difere do Publisher-Subscriber, em que todos os assinantes recebem todas as mensagens. A fila funciona como buffer e como mecanismo natural de balanceamento de carga: consumidores livres pegam a próxima mensagem, enquanto mensagens demoradas não bloqueiam as demais.

No Enterprise Integration Patterns (Hohpe e Woolf), o padrão é descrito como vários consumidores em um mesmo canal ponto a ponto, em que o sistema de mensageria decide qual deles recebe cada mensagem. Ele só faz sentido em canais ponto a ponto: em um canal publish-subscribe, consumidores adicionais apenas recebem cópias de cada mensagem. O EIP destaca ainda que a concorrência real exige coordenação, o que pode custar desempenho em alto volume, e que a distribuição antecipada de trabalho pode deixar um consumidor ocioso enquanto outro acumula fila. Em sistemas no estilo Kafka, o paralelismo é limitado pelo número de partições (um consumidor por partição dentro do grupo), e as mensagens são atribuídas às partições por um roteador, normalmente baseado na chave da mensagem, o que preserva a ordem de mensagens relacionadas.

Quando o sistema de mensageria oferece leitura transacional ou bloqueio temporário (por exemplo, peek-lock), uma mensagem cujo processamento falha volta a ficar visível e pode ser processada por outra instância. Mensagens que falham repetidamente devem ser desviadas para uma fila de mensagens mortas (dead-letter queue).

Problemas que resolve​

  • Volume de requisições altamente variável e imprevisível, que sobrecarrega uma única instância consumidora em horários de pico.
  • Processamento síncrono de requisições que bloqueia a lógica de negócio da aplicação.
  • Perda de mensagens ou de trabalho quando um consumidor único falha e o produtor depende dele diretamente.
  • Necessidade de coordenar múltiplos consumidores para que cada mensagem seja processada por apenas um deles, com carga distribuída de forma equilibrada.
  • Superprovisionamento de capacidade para atender picos que ocorrem só ocasionalmente.

Quando usar​

  • A carga de trabalho pode ser dividida em tarefas que rodam de forma assíncrona.
  • As tarefas são independentes entre si e podem ser processadas em paralelo.
  • O volume de trabalho varia muito e exige uma solução escalável (inclusive escalando até zero quando a fila está vazia).
  • A solução precisa de alta disponibilidade e resiliência quando o processamento de uma tarefa falha.

Quando evitar​

  • Não é fácil separar a carga em tarefas discretas, ou há forte dependência entre as tarefas.
  • As tarefas precisam ser executadas de forma síncrona e a aplicação deve aguardar o término de cada uma antes de prosseguir.
  • As tarefas precisam ser executadas em uma sequência específica (a menos que o sistema de mensageria ofereça sessões ou agrupamento que direcionem mensagens relacionadas ao mesmo consumidor).

Trade-offs​

GanhoCusto / Risco
Maior vazão por processamento paraleloOrdem de processamento não garantida
Redundância: a falha de um consumidor não interrompe o processamentoEntrega "pelo menos uma vez" exige consumidores idempotentes
Escala elástica com base na profundidade da fila, reduzindo custo em períodos ociososInfraestrutura de mensageria adicional para operar e monitorar
Desacoplamento entre produtores e consumidoresRetorno de resultados ao produtor exige canal extra (fila de resposta ou armazenamento compartilhado) e correlação
Nivelamento de carga em picosA própria fila pode virar gargalo em alta escala
  • Ordenação: projete o sistema para não depender da ordem das mensagens; quando a ordem importa, use sessões ou partições que fixem grupos de mensagens em um único consumidor.

  • Idempotência: como uma mensagem pode ser processada mais de uma vez (reentregas após falhas ou timeouts), as operações devem ser idempotentes.

  • Mensagens envenenadas: mensagens malformadas ou que dependem de recursos indisponíveis devem ser movidas para uma dead-letter queue após um número máximo de tentativas, em vez de voltarem à fila indefinidamente.

  • Tratamento de resultados: se o produtor precisar do resultado, armazene-o em local acessível a ambos ou use uma fila de resposta, sinalizando claramente quando o processamento terminou.

  • Escala da mensageria: em grande volume, particione as filas ou distribua mensagens entre várias filas para evitar que a fila vire gargalo.

  • Confiabilidade da mensageria: use um sistema de mensagens durável que garanta que nenhuma mensagem enfileirada seja perdida.

  • Custo: limite o número máximo de consumidores concorrentes para controlar gastos e proteger recursos compartilhados a jusante.

  • Observabilidade: monitore profundidade da fila, idade da mensagem mais antiga, taxa de falhas e volume da dead-letter queue.

  • Visão do EIP: os limites entre throughput e ordenação, e entre balanceamento e ociosidade, são intrínsecos ao padrão; consumidores que dividem partições não competem de fato, apenas dividem o trabalho de forma estática.

Diagrama​

Várias instâncias da aplicação publicam mensagens em uma única fila; cada mensagem é entregue a apenas um consumidor do pool, que pode escalar conforme a profundidade da fila. Mensagens que falham repetidamente são desviadas para a dead-letter queue para análise.

Padrões relacionados​

  • Queue-Based Load Leveling — a fila entre produtores e consumidores absorve picos e nivela a carga.
  • Publisher-Subscriber — contraste: no Pub/Sub todos os assinantes recebem cada mensagem; aqui, apenas um consumidor.
  • Idempotent Consumer — garante que reentregas de mensagens não causem efeitos duplicados.
  • Priority Queue — permite que consumidores processem primeiro as mensagens mais importantes.
  • Sequential Convoy — processa em ordem grupos de mensagens relacionadas sem abrir mão da concorrência entre grupos.
  • Asynchronous Request-Reply — forma de devolver resultados ao produtor quando o processamento é assíncrono.
  • Compute Resource Consolidation — consumidores podem ser consolidados em unidades de computação compartilhadas.
  • Point-to-Point Channel — o tipo de canal em que os consumidores competem (EIP).
  • Message Dispatcher — alternativa em que um único consumidor distribui o trabalho a executores (EIP).
  • Polling Consumer — modo de consumo possível para cada concorrente (EIP).
  • Event-Driven Consumer — outro modo de consumo possível (EIP).
  • Retry — trata falhas transitórias durante o processamento das mensagens.
  • Throttling — limita o consumo de recursos quando a escala dos consumidores pressiona serviços a jusante.

Referência​