Mensageria e streaming de eventos
Middleware orientado a mensagens: filas, tópicos e comunicação assíncrona
Iniciante12 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
O middleware orientado a mensagens permite que aplicações troquem dados por meio de um broker em vez de chamarem umas às outras diretamente. Ao final deste guia, você saberá explicar os modelos ponto a ponto e publish-subscribe, identificar as partes de uma mensagem, comparar os principais protocolos e brokers e avaliar quando a mensageria assíncrona é o estilo de integração certo para um problema.
O que você vai aprender
- Comparar os quatro estilos de integração e explicar o que a mensageria acrescenta em relação à transferência de arquivos, ao banco de dados compartilhado e às chamadas remotas
- Descrever a anatomia de uma mensagem e os papéis de produtor, broker, destino e consumidor
- Diferenciar filas ponto a ponto de tópicos publish-subscribe e escolher o modelo certo para cada caso de uso
- Explicar as três formas de desacoplamento que um broker oferece e quanto elas custam
- Situar JMS, AMQP, STOMP, MQTT e o protocolo do Kafka uns em relação aos outros
- Escolher entre ActiveMQ, RabbitMQ e Kafka para uma determinada carga de trabalho
Duas aplicações podem compartilhar dados trocando arquivos, lendo o mesmo banco de dados, chamando uma à outra pela rede ou enviando mensagens uma para a outra por meio de um intermediário. Essa última opção — o middleware orientado a mensagens (MOM) — é a que permite que cada lado continue funcionando quando o outro está lento, sobrecarregado ou fora do ar. É essa propriedade que faz da mensageria a espinha dorsal do processamento de pedidos, dos pipelines de pagamento, da coleta de logs e da maioria das arquiteturas orientadas a eventos. Este guia explica o modelo que todos os brokers compartilham, para que os guias seguintes sobre JMS, ActiveMQ, RabbitMQ e Kafka façam sentido como variações de um mesmo tema, e não como quatro produtos sem relação entre si.
Quatro estilos de integração, um trade-off
Todo problema de integração se resume a mover dados e disparar comportamento entre sistemas que não foram escritos juntos. A classificação clássica lista quatro estilos, e cada um corrige uma fraqueza do anterior ao custo de criar outra.
| Estilo | Como funciona | Ponto forte | Ponto fraco |
|---|---|---|---|
| Transferência de arquivos | Um sistema grava um arquivo e outro o lê mais tarde | Simples, sem runtime compartilhado, qualquer linguagem | Latência de horas, desvio de formato, nenhum feedback |
| Banco de dados compartilhado | Várias aplicações leem e gravam o mesmo schema | Dados sempre consistentes e atualizados | O schema vira um contrato que ninguém consegue mudar; contenção |
| Chamada de procedimento remoto (HTTP, gRPC, RMI) | Uma aplicação chama uma função exposta por outra | Resposta imediata, modelo de programação familiar | Quem chama fica bloqueado; os dois lados precisam estar no ar; falhas em cascata |
| Mensageria | As aplicações entregam pequenos pacotes a um broker, que os distribui | Assíncrona, confiável, desacoplada | Consistência eventual, depuração mais difícil, mais peças móveis |
Mensageria não é "melhor" do que chamadas remotas. É um contrato diferente: o remetente só fica sabendo que o broker aceitou a mensagem, não que alguém a processou. Por isso, sistemas construídos sobre mensageria precisam lidar bem com trabalho que acontece depois e com resultados que chegam como novas mensagens, e não como valores de retorno.
Anatomia de uma mensagem
Seja qual for o broker, uma mensagem tem as mesmas três partes.
- Os headers são campos que o próprio broker entende e sobre os quais pode agir: um identificador único, um timestamp, um destino, uma prioridade, uma expiração, um modo de entrega (o broker deve gravar a mensagem em disco antes de confirmá-la?) e, para request-reply, um endereço de resposta e um identificador de correlação.
- As propriedades (que o RabbitMQ e o Kafka também chamam de headers) são pares chave-valor definidos pela aplicação. Consumidores podem filtrar por elas, roteadores podem tomar decisões com base nelas e sistemas de tracing propagam nelas os identificadores de trace. É ali que ficam metadados como
tenant,eventTypeouschemaVersion. - O corpo (payload) é, para o broker, uma sequência opaca de bytes. JSON é a codificação mais comum hoje; formatos binários como Avro ou Protocol Buffers são preferidos em alto volume porque são menores e carregam um schema.
Uma boa disciplina é colocar tudo o que um roteador ou um filtro precisa nos headers ou nas propriedades, e nunca obrigar ninguém a interpretar o corpo para decidir para onde uma mensagem vai. Brokers não conseguem olhar dentro do corpo, e o código que faz isso fica acoplado ao formato do payload.
{
"headers": {
"messageId": "ID:app-1-45231-1725700000000-1:1:1:1",
"timestamp": 1725700000000,
"deliveryMode": "PERSISTENT",
"replyTo": null,
"correlationId": null
},
"properties": {
"eventType": "OrderPlaced",
"schemaVersion": "2",
"tenant": "eu-west"
},
"body": "{\"orderId\":\"o-9911\",\"total\":149.90,\"currency\":\"EUR\"}"
}
Os identificadores nos headers são o que permite a um consumidor detectar duplicatas e o que liga uma resposta à sua requisição; você pode gerar os seus com um gerador de UUID quando o broker não atribui um.
Produtores, consumidores, destinos e o broker
O vocabulário é pequeno. Um produtor (publisher, remetente) cria uma mensagem e a entrega ao broker. O broker a armazena em um destino e a entrega a um ou mais consumidores (subscribers, receptores). O produtor nunca fica sabendo quem são os consumidores, e os consumidores nunca sabem quem produziu a mensagem: os dois só conhecem o nome do destino.
O broker é o componente que torna isso confiável. Ele aceita mensagens mesmo quando nenhum consumidor está conectado, as persiste se for solicitado, as reentrega quando um consumidor falha e controla quem pode enviar para um destino ou ler dele. Ele também é um ponto único de monitoramento: profundidade das filas, número de consumidores e throughput ficam visíveis em um só lugar.
Nem todo sistema de mensageria precisa de um broker. Bibliotecas sem broker, como o ZeroMQ, entregam diretamente entre processos e ganham em latência, mas aí não há onde a mensagem esperar enquanto o destinatário está fora do ar. Assim que você precisa de uma fila compartilhada por várias aplicações e que sobreviva a reinicializações, você precisa de um broker.
Dois domínios de mensageria: filas e tópicos
Os destinos vêm em dois tipos, e escolher o certo é a primeira decisão de design de qualquer sistema de mensageria.
Ponto a ponto: a fila
Uma fila (queue) entrega cada mensagem a exatamente um consumidor. Se dez consumidores leem da mesma fila, o broker distribui as mensagens entre eles — um padrão chamado competing consumers —, e é assim que se escala o throughput horizontalmente: adicione um consumidor e a fila esvazia mais rápido. As mensagens esperam na fila até que alguém as leia, então produtor e consumidor não precisam estar rodando ao mesmo tempo. Processamento de pedidos, jobs de redimensionamento de imagens e envio de e-mails são cargas típicas de fila: cada unidade de trabalho precisa ser feita uma vez.
Publish-subscribe: o tópico
Um tópico (topic) entrega cada mensagem a todos os assinantes ativos. Um único evento "endereço do cliente alterado" pode chegar ao sistema de faturamento, ao sistema de entregas e ao pipeline de analytics sem que o produtor saiba que eles existem. Por padrão, uma assinatura só recebe mensagens publicadas enquanto está ativa; uma assinatura durável pede ao broker que guarde as mensagens para um assinante nomeado que está temporariamente offline. Sem durabilidade, um tópico é um broadcast; com ela, cada assinante tem, na prática, uma fila privada.
| Fila | Tópico | |
|---|---|---|
| Consumidores por mensagem | Um | Zero ou vários |
| Consumidor offline | A mensagem espera | A mensagem se perde, a menos que a assinatura seja durável |
| Escalar consumidores | Aumenta o throughput | Adiciona cópias |
| Uso típico | Distribuição de trabalho, comandos | Notificação de eventos, broadcast |
Os brokers modernos deixam essa fronteira menos nítida: o RabbitMQ constrói os dois a partir de exchanges e bindings, e o Kafka oferece os dois por meio de partições e consumer groups. As duas intenções — faça isto uma vez versus avise todo mundo — continuam sendo a forma certa de pensar um design, seja qual for o produto.
O que o desacoplamento realmente oferece
"Acoplamento fraco" é a justificativa padrão para a mensageria, mas ela esconde três benefícios distintos, cada um com seu preço.
Desacoplamento no tempo. O consumidor não precisa estar rodando quando o produtor envia. Um sistema batch noturno pode aceitar eventos o dia inteiro; um provedor de pagamentos que está em manutenção recebe seu backlog quando volta. O preço é que o produtor ainda não pode garantir ao usuário que algo aconteceu.
Desacoplamento no espaço. O produtor endereça um nome de destino, não um host. Você pode mover um consumidor para outra máquina, rodar cinco cópias dele ou substituí-lo por um serviço reescrito, e a configuração do produtor não muda. O preço é que o broker vira infraestrutura crítica e precisa da sua própria estratégia de alta disponibilidade.
Desacoplamento de carga. Uma fila absorve picos. Um front-end web que recebe dez mil pedidos em um minuto consegue enfileirá-los em segundos e deixar o serviço de fulfillment processá-los no seu próprio ritmo, em vez de estourar o timeout no pico. O preço é que a fila pode crescer sem limite se os consumidores ficarem para trás, então todo sistema precisa de um plano para o backlog: mais consumidores, expiração de mensagens ou backpressure sobre o produtor.
Garantias de entrega em um parágrafo
Um broker pode oferecer entrega at-most-once (fire and forget: rápida, mas pode perder mensagens), entrega at-least-once (o broker guarda a mensagem até que o consumidor a confirme, de modo que uma queda antes da confirmação provoca uma reentrega e possivelmente uma duplicata) ou uma forma de exactly-once limitada a um escopo específico (as transações do Kafka entre tópicos do Kafka, por exemplo). A entrega persistente — gravar a mensagem em disco antes de confirmá-la ao produtor — faz o at-least-once sobreviver a uma reinicialização do broker. Quase todo sistema em produção roda com at-least-once e torna seus consumidores idempotentes, ou seja, processar a mesma mensagem duas vezes tem o mesmo efeito que processá-la uma vez. O guia de padrões, mais adiante neste assunto, mostra como.
Protocolos e APIs
A palavra "mensageria" abrange uma API padrão, vários protocolos de rede e um protocolo específico de um produto; confundi-los causa erros de integração bem reais.
- JMS (Jakarta Messaging) é uma API Java, não um protocolo. Código escrito com
jakarta.jmsfunciona com qualquer broker que forneça um cliente JMS, mas um cliente JMS conversa com seu broker no formato de rede que o fornecedor escolheu. Dois brokers não são interoperáveis só porque ambos "suportam JMS". - AMQP 0-9-1 é o protocolo de rede em torno do qual o RabbitMQ foi construído; ele define exchanges, filas, bindings e channels, e qualquer linguagem com um cliente AMQP 0-9-1 pode conversar com o RabbitMQ.
- AMQP 1.0 é um padrão OASIS e ISO que compartilha pouco além do nome com o 0-9-1. Ele define como pares transferem mensagens, não uma topologia de broker. ActiveMQ Classic, ActiveMQ Artemis, RabbitMQ (nativamente desde a 4.0) e Azure Service Bus falam esse protocolo.
- STOMP é um protocolo de texto minimalista que dá para operar a partir de uma sessão Telnet. É popular para clientes de navegador sobre WebSocket e para linguagens de script.
- MQTT é um protocolo publish-subscribe compacto, projetado para dispositivos com recursos limitados e redes pouco confiáveis; tanto o ActiveMQ quanto o RabbitMQ aceitam conexões MQTT.
- O protocolo do Kafka é o protocolo binário próprio do Kafka. Não há padrão por trás dele, mas existem clientes para todas as linguagens mais usadas.
- OpenWire é o protocolo binário nativo do ActiveMQ Classic, usado pelos seus clientes Java, .NET e C++.
Para um time Java, a consequência prática é: use JMS quando quiser manter a opção de trocar de broker, e o cliente nativo (amqp-client do RabbitMQ, kafka-clients do Kafka) quando quiser todos os recursos do broker escolhido.
Como escolher um broker
Os três brokers open source que este assunto cobre resolvem problemas que se sobrepõem, mas não são iguais.
| ActiveMQ Classic (e Artemis) | RabbitMQ | Apache Kafka | |
|---|---|---|---|
| Modelo | Filas e tópicos JMS | Exchanges roteiam para filas | Log particionado e replicado |
| Vida útil da mensagem | Apagada na confirmação | Apagada na confirmação | Retida por um tempo configurado, independentemente do consumo |
| Ordenação | Por fila (por grupo de mensagens com JMSXGroupID) | Por fila com um único consumidor | Por partição |
| Reprocessar mensagens antigas | Não | Não (exceto streams) | Sim, reposicionando os offsets |
| Flexibilidade de roteamento | Seletores, virtual topics, destinos compostos | Muito alta (tipos de exchange, bindings, headers) | Baixa: o produtor escolhe um tópico e uma chave |
| Faixa ideal de throughput | De milhares a dezenas de milhares de mensagens/s | Parecida, com roteamento rico | De centenas de milhares a milhões de registros/s |
| Protocolos | OpenWire, AMQP 1.0, STOMP, MQTT, WebSocket | AMQP 0-9-1, AMQP 1.0, STOMP, MQTT, streams | Protocolo do Kafka |
| Uso natural | Aplicações Java EE e Spring que precisam de JMS | Filas de tarefas, RPC, roteamento flexível, times poliglotas | Event streaming, pipelines de logs, integração de dados, reprocessamento |
Algumas regras práticas se sustentam bem. Se seus consumidores são message-driven beans JMS ou métodos Spring @JmsListener e você precisa de filas transacionais, o ActiveMQ é o caminho com menos atrito; prefira o Artemis em novas implantações que precisem de mais throughput ou de clustering, e o Classic onde já houver configuração e ferramentas existentes. Se você precisa rotear uma mensagem para filas diferentes de acordo com vários atributos, ou tem produtores em Python e consumidores em Go, o modelo de exchanges do RabbitMQ é difícil de bater. Se os dados são um fluxo de eventos que vários times vão consumir em momentos diferentes, que precisa poder ser reprocessado ou que chega em volume muito alto, o log retido do Kafka é a primitiva certa, e forçá-lo a se comportar como uma fila de tarefas vai decepcionar você.
Onde a mensageria dá errado
- Usar uma fila como banco de dados. Filas são para mensagens em trânsito. Guardar estado em uma fila e consultá-lo depois leva a crescimento sem limite e a uma ordem imprevisível. Persista o estado em um store; coloque os eventos na fila.
- Supor ordenação entre consumidores. Uma fila com três consumidores concorrentes entrega em ordem, mas processa em paralelo, então a mensagem 2 pode terminar antes da mensagem 1. Se a ordem importa, use um consumidor por chave (grupos de mensagens, single-active-consumer do RabbitMQ, partições do Kafka).
- Mensagens muito grandes. Um PDF de 50 MB no corpo de uma mensagem deixa cada salto mais lento e estoura os limites de memória do broker. Guarde o arquivo em um object storage e envie uma referência — é o padrão claim-check.
- Ignorar o backlog. Uma fila que ninguém monitora enche em silêncio até o broker atingir o limite de disco e parar de aceitar mensagens de todos os produtores. Crie alertas de profundidade de fila e de número de consumidores desde o primeiro dia.
- Acoplamento pelo payload. Se todos os consumidores interpretam o mesmo JSON sem documentação, o sistema "desacoplado" não consegue mudar um campo sem um deploy coordenado. Versione o payload e coloque uma propriedade
schemaVersionem toda mensagem.
Principais pontos
- Mensageria é um dos quatro estilos de integração; ela troca respostas imediatas por resiliência, elasticidade e independência entre sistemas.
- Uma mensagem é composta de headers que o broker entende, propriedades que a aplicação define e um corpo opaco.
- Filas entregam cada mensagem uma vez e escalam com consumidores concorrentes; tópicos fazem broadcast para todos os assinantes, de forma durável se solicitado.
- Desacoplamento no tempo, no espaço e de carga é o que um broker oferece; backlogs sem limite e uma peça crítica de infraestrutura são o que ele custa.
- JMS é uma API Java, AMQP, STOMP e MQTT são protocolos de rede, e o Kafka fala o seu próprio; não presuma interoperabilidade por causa de uma sigla em comum.
- O ActiveMQ se encaixa em sistemas Java centrados em JMS, o RabbitMQ em sistemas poliglotas com muito roteamento e o Kafka em fluxos de eventos de alto volume que precisam ser reprocessáveis.
Fontes e leituras complementares
Perguntas frequentes
- Um message broker é a mesma coisa que um enterprise service bus?
- Não. Um broker armazena e encaminha mensagens entre destinos, sem entender o conteúdo delas. Um enterprise service bus fica em cima de um broker ou de um transporte e acrescenta roteamento baseado em conteúdo, transformação, ponte entre protocolos e orquestração. Muitos produtos de ESB embutem um broker como o ActiveMQ, mas um broker pode rodar sozinho, e é assim na maioria dos sistemas modernos.
- Quando uma simples chamada HTTP é melhor do que uma mensagem?
- Quando quem chama realmente precisa da resposta para continuar — validar um cartão antes de confirmar um pedido, por exemplo — e espera-se que o serviço chamado esteja disponível. A mensageria se destaca quando o trabalho pode ser adiado, quando produtores e consumidores escalam em ritmos diferentes, quando o mesmo evento interessa a vários consumidores ou quando o sistema de destino às vezes fica indisponível.
- Uma fila garante que uma mensagem seja processada exatamente uma vez?
- Sozinha, não. Um broker pode prometer entrega at-most-once ou at-least-once; o consumidor ainda pode cair depois de processar e antes de confirmar, e aí a mesma mensagem é entregue de novo. O processamento exactly-once é uma propriedade do pipeline inteiro e normalmente vem de consumidores idempotentes ou de um controle transacional do lado do consumidor.
- O Kafka é uma fila de mensagens?
- O Kafka é um commit log distribuído e replicado. Ele permite dividir trabalho como uma fila por meio de consumer groups e fazer pub-sub com grupos independentes, mas não apaga um registro quando ele é consumido nem controla confirmações por mensagem. Essa diferença de modelo é o motivo pelo qual o Kafka costuma ser descrito como uma plataforma de event streaming, e não como uma fila.