A escolha aparece quando um endpoint síncrono começa a segurar a requisição por tempo demais: envio de e-mail, geração de relatório, chamada a um serviço externo instável. A resposta é tirar esse trabalho do caminho da requisição e jogá-lo numa fila. A pergunta imediata é qual broker colocar ali. RabbitMQ e Kafka resolvem problemas que se sobrepõem em alguns pontos e divergem em quase todo o resto. Escolher pelo mais popular do momento é como escolher banco de dados pelo logo: funciona até a primeira carga que o modelo não comporta.
Este texto coloca os dois lado a lado pelo critério que importa na hora de decidir — o modelo de consumo — e termina cravando qual usar em cada cenário.
O ponto onde os dois divergem: fila versus log
RabbitMQ é um message broker no sentido clássico. A mensagem entra numa fila, um consumidor a retira, processa e confirma (ack). Confirmada, a mensagem desaparece da fila. O broker é responsável por rotear, entregar e esquecer. O roteamento é rico: exchanges do tipo direct, topic e fanout permitem que a mesma mensagem vá para filas diferentes conforme uma routing key, sem que o produtor saiba quem consome.
Kafka não é uma fila. É um log distribuído e particionado. A mensagem é anexada ao fim de uma partition e permanece lá pelo tempo de retenção configurado — horas, dias ou indefinidamente — independentemente de ter sido lida. O consumidor não remove nada; ele mantém um offset, a posição de leitura dentro da partition. Ler significa avançar o offset. Isso muda tudo: vários consumidores independentes leem o mesmo dado em ritmos diferentes, e reprocessar o histórico é só mover o offset para trás.
Dessa diferença — apagar após consumir contra reter e reler — derivam quase todas as outras. Vale fixar isso antes de olhar throughput ou lat; é o eixo em que a decisão realmente se define.
Ordenação, throughput e entrega
No RabbitMQ, a ordem se mantém dentro de uma fila com um único consumidor. No momento em que você escala para vários consumidores concorrentes na mesma fila para ganhar vazão, a ordem de processamento deixa de ser garantida — duas mensagens saem quase juntas e terminam fora de sequência. Se a ordem importa, você fica preso a um consumidor por fila, o que limita o paralelismo.
Kafka resolve isso com particionamento por chave. Todas as mensagens com a mesma chave (por exemplo, o mesmo user_id) caem na mesma partition e são lidas em ordem, enquanto partitions diferentes são consumidas em paralelo por membros distintos de um consumer group. Você ganha ordem e paralelismo, ao custo de dimensionar o número de partitions com antecedência — aumentar depois é possível, mas quebra a garantia de ordenação para chaves já existentes.
Em vazão bruta, Kafka opera em outra faixa. Escrita sequencial em disco e leitura em lote fazem centenas de milhares a milhões de mensagens por segundo serem viáveis num cluster modesto. RabbitMQ entrega dezenas de milhares por segundo com folga na maioria dos cenários; passar disso exige cuidado com filas longas, que degradam a latência porque o broker mantém estado por mensagem. A ordem de grandeza varia com hardware e tamanho de payload, mas a assimetria é real: Kafka foi desenhado para o regime de alto volume contínuo.
channel.basic_qos(prefetch_count=10)
for method, props, body in channel.consume('emails'):
try:
enviar(body)
channel.basic_ack(method.delivery_tag)
except Exception:
channel.basic_nack(method.delivery_tag, requeue=True)
Com prefetch_count baixo, o broker não despeja mil mensagens num worker lento; distribui conforme cada um confirma.
Entrega: o que cada um garante de verdade
Os dois oferecem at-least-once como padrão realista — a mensagem chega uma ou mais vezes, e o consumidor precisa ser idempotente. RabbitMQ confirma o consumo com ack; se o worker cai antes de confirmar, a mensagem volta à fila e pode ser entregue de novo. Kafka reprocessa a partir do último offset commitado; se o commit não aconteceu, a mesma faixa é relida.
Kafka oferece ainda um modo exactly-once dentro do seu ecossistema, com produtores transacionais e commit de offset atrelado à escrita de saída. Funciona, mas custa complexidade e só vale a pena quando o processamento não pode ser idempotente por outros meios. Na prática, tornar o consumidor idempotente — uma chave de deduplicação, um upsert em vez de insert — resolve a maioria dos casos nos dois brokers e é mais barato que perseguir entrega exata na camada de transporte.
Operação e custo de manter
RabbitMQ é mais simples de subir e operar em escala pequena e média. Um nó único atende bem muitos projetos; clustering e filas espelhadas (ou quorum queues, hoje o caminho recomendado para alta disponibilidade) existem quando a durabilidade exige. A interface de administração é boa e o modelo mental — produtor, exchange, fila, consumidor — é direto.
Kafka carrega mais peso operacional. Versões recentes eliminaram a dependência do ZooKeeper com o modo KRaft, o que simplificou o cluster, mas ainda há partitions para dimensionar, retenção para calibrar, fatores de replicação para definir e consumer groups para monitorar por lag. Nada disso é proibitivo, mas é trabalho contínuo que um broker de fila clássico não cobra. Rodar Kafka gerenciado por um provedor transfere boa parte desse custo, ao preço da conta mensal.
Esse peso operacional muda de sinal conforme a maturidade do time. Uma equipe pequena que ainda não tem prática com sistemas distribuídos paga caro por cada partition mal dimensionada e por cada consumer group sem monitoramento de lag — problemas que aparecem em produção, sob carga, no pior momento. Já um time que roda observabilidade decente e trata o broker como infraestrutura de primeira classe absorve o custo do Kafka sem sobressalto e colhe o throughput. O ponto é honesto: a complexidade do Kafka não é um defeito, é o preço de recursos que o RabbitMQ não oferece. A pergunta certa não é qual é mais simples, e sim se o seu caso precisa daquilo que a complexidade compra. Escolher a ferramenta mais poderosa para um problema que não a exige é herdar o custo sem o benefício.
Comparativo direto
| Critério | RabbitMQ | Kafka |
|---|---|---|
| Modelo | Fila: consome e apaga | Log: retém e relê por offset |
| Reprocessar histórico | Não nativo | Mover offset para trás |
| Roteamento | Rico (topic, fanout, direct) | Simples (tópico + partition por chave) |
| Ordem + paralelismo | Excludentes na mesma fila | Ambos, via partition por chave |
| Vazão típica | Dezenas de milhares/s | Centenas de milhares/s ou mais |
| Múltiplos leitores do mesmo dado | Exige fanout e cópias | Nativo (vários consumer groups) |
| Custo operacional | Baixo a médio | Médio a alto |
Qual usar em qual cenário
Use RabbitMQ quando o problema é distribuir tarefas para workers processarem e esquecerem: envio de notificações, geração de PDF, chamadas assíncronas a APIs externas, orquestração de jobs com roteamento por tipo. O modelo de fila com ack casa exatamente com esse trabalho, e o roteamento por exchange evita código de dispatch na aplicação. É também a escolha default quando o volume está na casa dos milhares por segundo e a simplicidade operacional pesa mais que o teto de throughput.
Use Kafka quando o dado é um fluxo que vários sistemas precisam consumir de forma independente, quando o volume é alto e contínuo, ou quando reprocessar o histórico faz parte do desenho: pipelines de eventos, ingestão de métricas e logs, alimentação de um data lake, event sourcing, sincronização entre serviços por change data capture. A capacidade de reler e de ter múltiplos consumer groups sobre o mesmo tópico é o que justifica o custo operacional adicional.
O erro comum é usar Kafka como fila de tarefas simples — herdando toda a complexidade sem usar o log — ou espremer RabbitMQ num pipeline de streaming de alto volume com múltiplos consumidores, recriando à mão o que o log entrega de graça. Se a decisão ainda estiver empatada, comece por RabbitMQ: o modelo mental é mais barato e migrar um caso de tarefa assíncrona para Kafka depois é viável; o contrário, desmontar um pipeline de streaming acoplado ao log, quase nunca é.