02 - RabbitMQ (Fila tradicional)
video: https://youtu.be/... no frontmatter da nota.RabbitMQ (Fila tradicional)
RabbitMQ é um message broker baseado no protocolo AMQP 0-9-1, desenhado em torno do modelo de filas tradicionais com roteamento flexível. O produtor publica em um exchange, o exchange encaminha a mensagem para filas de acordo com regras de binding, e o consumidor retira da fila com confirmação explícita de processamento. É o broker que carrega décadas de uso em produção para filas de tarefas, integração entre sistemas e distribuição de trabalho, com reputação de confiabilidade e de ferramental operacional maduro. Sua relevância em arquitetura de produção vem do equilíbrio: throughput suficiente para a maioria das cargas, latência baixa por mensagem, semântica de fila clássica com ack e requeue, e recursos de roteamento que dispensam código de despacho no produtor. Para times que precisam de fila de trabalho com controle fino de roteamento e sem a complexidade de partições e offsets, RabbitMQ costuma ser a resposta direta.
O modelo AMQP
O AMQP 0-9-1 separa a publicação da fila em três conceitos. O produtor publica a mensagem em um exchange, sempre; nunca direto em uma fila. O exchange roteia a mensagem para filas conforme bindings, que são regras configuradas entre exchange e fila. O consumidor consome de uma fila com confirmação explícita, o ack, que é o que remove a mensagem definitivamente.
Essa separação dá ao RabbitMQ sua principal qualidade: roteamento declarativo. O produtor publica uma mensagem com routing key e attributes, e a decisão de destino fica na topologia do broker, configurável sem redeploys de código. Novos consumidores com interesses novos entram adicionando binding, e o produtor nem fica sabendo.
As mensagens carregam propriedades além do corpo: content_type, headers, delivery_mode, que controla persistência em disco, priority e expiration. Persistência no RabbitMQ é dupla: mensagens marcadas como persistentes e filas declaradas como duráveis sobrevivem a restart do broker; faltar uma das duas condições perde dados no restart, e é um dos erros mais comuns de configuração.
# Consumidor com ack manual e rejeição controlada (pseudocódigo)
connection = amqp.conectar(host="broker", vhost="producao")
canal = connection.channel()
canal.basic_qos(prefetch_count=1) # distribuição justa entre workers
def callback(ch, metodo, propriedades, corpo):
try:
processar(corpo) # trabalho real, ex: gerar PDF
ch.basic_ack(delivery_tag=metodo.delivery_tag) # remove da fila
except ErroDeContrato as e:
# falha permanente: reprocessar não resolve, vai direto para DLQ
ch.basic_nack(delivery_tag=metodo.delivery_tag, requeue=False)
log.erro("mensagem para DLQ", motivo=str(e), msg_id=metodo.message_id)
except ErroTransitorio:
# falha temporária: requeue com contagem no header, limita tentativas
tentativas = propriedades.headers.get("x-retry", 0) + 1
if tentativas >= 5:
ch.basic_nack(delivery_tag=metodo.delivery_tag, requeue=False)
else:
republicar_com_atraso(corpo, tentativas) # fila de retry com TTL
ch.basic_ack(delivery_tag=metodo.delivery_tag)
canal.basic_consume(queue="pedidos.faturamento", on_message_callback=callback)
canal.start_consuming()
Exchanges: direct, fanout e topic
O exchange direct entrega a mensagem às filas cujo binding tem exatamente a mesma routing key da mensagem. É o roteamento ponto a ponto: routing key pagamento.aprovado vai para a fila que se inscreveu nessa chave. Serve para despacho direto quando o produtor conhece o tipo da mensagem.
O exchange fanout ignora a routing key e entrega cópias a todas as filas vinculadas. É o broadcast de trabalho ou de eventos: cada fila recebe a mensagem completa, e o processamento de uma não interfere nas outras. É o equivalente do padrão publish-subscribe em forma mais simples.
O exchange topic roteia por padrões de chave com wildcards: o asterisco substitui uma palavra, a cerquilha substitui zero ou mais palavras. Bindings como pedido.*.aprovado ou log.# dão flexibilidade de hierarquia, e é o tipo mais usado quando o domínio de eventos cresce e novos consumidores aparecem com recortes diferentes do mesmo fluxo. O exchange headers roteia por atributos do header em vez da routing key, útil quando o critério de roteamento não cabe em uma string hierárquica.
| Exchange | Critério de roteamento | Uso típico |
|---|---|---|
| direct | Igualdade exata de routing key | Despacho de comandos específicos |
| fanout | Ignora routing key, envia a todos os bindings | Broadcast de eventos para N filas |
| topic | Padrões com * e # na routing key | Hierarquia de eventos com recortes por interesse |
| headers | Atributos do header da mensagem | Roteamento por múltiplos critérios não hierárquicos |
Acknowledge, requeue e confiabilidade
O ciclo de confiabilidade do RabbitMQ tem três pontos de confirmação. O produtor ativa publisher confirms e recebe do broker a confirmação de que a mensagem chegou e foi tratada; sem isso, a publicação é fire and forget, e falha de rede apaga mensagens silenciosamente. O broker persiste conforme as flags. O consumidor confirma com basic.ack após processar; enquanto não chega ack, a mensagem permanece marcada como não confirmada e volta para a fila se a conexão do consumidor cai.
O requeue é o comportamento que precisa de cuidado. Quando o consumidor rejeita com basic.nack ou basic.reject e pede requeue, a mensagem volta ao topo da fila imediatamente, e um consumidor com bug reprocessa em loop infinito a mesma falha. O recurso que doma esse problema é a dead letter exchange: uma fila pode declarar que mensagens rejeitadas, expiradas ou excedentes vão para outra exchange, formando a fila de mortos com contexto de motivo.
Prefetch é o controle de fluxo do consumidor: define quantas mensagens não confirmadas ele pode ter em mãos ao mesmo tempo. Sem prefetch, o broker empurra mensagens sem parar e desbalanceia carga entre consumidores; com prefetch baixo demais, o throughput sofre por round trip. O ajuste por perfil de processamento é rotina de operação, e prefetch 1 é o padrão para trabalho pesado com distribuição justa.
TTL, prioridades e ordem
O RabbitMQ suporta TTL por mensagem e por fila, descartando ou roteando para dead letter o conteúdo expirado, o que implementa padrões como retry atrasado: a fila de retry tem TTL e dead letter apontando de volta para a fila principal, criando backoff sem timers no código. Prioridades por mensagem reordenam a entrega dentro da fila declarada com suporte a prioridade, útil para separar trabalho urgente do volume comum.
A ordem de entrega é FIFO no caso normal de fila única com um consumidor. Com múltiplos consumidores, prioridades, requeue e TTL, a ordem deixa de ser garantida de fato, e fluxos que dependem de sequência precisam de desenho próprio, como uma fila por entidade e concorrência controlada por chave. Comparado ao Kafka, o RabbitMQ não oferece ordem por partição com offsets; a ordem é consequência do desenho, não do contrato.
Tipos de fila e escolha operacional
| Tipo de fila | Característica | Quando usar |
|---|---|---|
| Clássica | Modelo original, réplica sem líder eleito consistente | Cargas leves, compatibilidade legada |
| Quorum | Réplica por consenso Raft, failover previsível | Dados que não podem se perder em produção |
| Stream | Log appendido com offsets, similar ao Kafka | Novos requisitos de replay dentro do RabbitMQ |
| Lazy | Mensagens vão direto ao disco, memória estável | Backlogs longos com picos de memória |
A direção atual do projeto é clara: filas quorum substituem filas clássicas replicadas, e streams cobrem os casos de log. Times novos devem declarar quorum como padrão para dados importantes, e tratar clássica como exceção de compatibilidade, não como escolha. A migração de clássica para quorum exige recriar a fila e redirecionar bindings, e é planejável com política de consumo duplo durante a troca.
# Topologia de exemplo: pedidos com roteamento topic e DLQ
rabbitmqadmin declare exchange name=pedidos.ex type=topic
rabbitmqadmin declare queue name=pedidos.faturamento \
durable=true \
arguments='{"x-dead-letter-exchange":"pedidos.dlx","x-message-ttl":86400000}'
rabbitmqadmin declare queue name=pedidos.dlq durable=true
rabbitmqadmin declare binding source=pedidos.ex \
destination=pedidos.faturamento routing_key="pedido.*.aprovado"
rabbitmqadmin declare binding source=pedidos.dlx \
destination=pedidos.dlq routing_key="#"
# Políticas de cluster: fila quorum para durabilidade e DLQ no vhost de produção
rabbitmqctl set_policy filas-duraveis "pedidos\\..*" \
'{"ha-mode":"exactly","ha-params":2}'
Cenários reais de produção
Fila de jobs de aplicação: envio de notificações, geração de PDF e processamento de imagens são tarefas heterogêneas com picos. RabbitMQ distribui entre workers com prefetch ajustado por peso da tarefa, e a dead letter queue separa o que falhou de vez para o time investigar. É o caso em que RabbitMQ brilha: simplicidade de fila de trabalho com roteamento por tipo de job.
Integração com sistemas legados: um ERP expõe integração frágil e o time precisa amortizar. RabbitMQ recebe eventos do domínio via topic exchange, e filas por sistema legado aplicam o ritmo que o destino aguenta, com TTL e DLQ para o que não couber. O broker vira a fronteira de tolerância entre mundos com velocidades diferentes.
Distribuição de eventos para poucos interessados: um exchange fanout entrega o mesmo evento para três filas de departamentos, e a quarta fila entra amanhã sem tocar no produtor. Para dezenas de consumidores e janela de replay, Kafka seria melhor; para meia dúzia de interessados e fila de trabalho, RabbitMQ entrega o mesmo resultado com operação muito menor.
Quando escolher RabbitMQ
RabbitMQ é a escolha natural para filas de tarefas, roteamento flexível de mensagens, integração entre sistemas com ritmos distintos e cenários de latência baixa por mensagem. Times que precisam de topologia declarativa, como rotear por tipo de evento sem código de despacho, encontram no AMQP a ferramenta certa.
Ele é escolha menos natural para streams com múltiplos grupos independentes lendo o mesmo fluxo com replay, porque não tem modelo de log com offsets, e para volumes que exigem throughput de centenas de milhares de mensagens por segundo sustentados com retenção em disco. Nesses casos Kafka, ou variantes de stream broker, se pagam.
A operação costuma ser o argumento decisivo: clusters com quórum de filas, ferramental maduro de monitoramento por fila, políticas declaradas no broker e um modelo mental simples de fila e ack reduzem a barreira de entrada. O preço aparece em escalas extremas, onde partições, shovels e federations aumentam a complexidade mais rápido do que no modelo de log.
Um contraste útil para a decisão: no RabbitMQ a mensagem é entregue e removida, o que simplifica raciocinar sobre cada item; no Kafka a mensagem permanece e o consumidor avança por offset, o que simplifica reprocessar o fluxo. Fila de trabalho pede o primeiro modelo, stream de eventos pede o segundo, e confundir os dois é a origem de boa parte das arquiteturas de mensageria mal dimensionadas.
Erros comuns e boas práticas
- Consumir sem ack e sem prefetch: mensagens se perdem na queda do consumidor ou o broker desequilibra a carga; ack manual com prefetch calibrado é o mínimo operacional.
- Declarar fila não durável para dado que não pode se perder: restart do broker apaga tudo; durabilidade da fila, persistência da mensagem e publisher confirms precisam vir juntos.
- Requeue infinito de mensagem falha: consumidor com bug transforma a fila em loop; limite de tentativas via header e dead letter exchange resolvem com topologia, não com código.
- Roteamento complexo demais na topologia: bindings encadeados que ninguém entende viram dívida; documentar a topologia e preferir chaves de assunto bem definidas.
- Usar RabbitMQ como banco: retenção longa de milhões de mensagens degrada performance; para repositório de eventos com replay, o modelo correto é log particionado.
- Ignorar filas quorum: filas clássicas replicadas estão em caminho de descontinuação; filas quorum dão consistência de réplica e eleição de líder embutida em clusters.
- Não monitorar profundidade por fila: cada fila tem seu consumidor e seu SLA; alarme agregado esconde a fila travada atrás da média saudável.
- Misturar mensagens enormes no fluxo: payload de dezenas de MB bloqueia o pipeline; a prática comum é passar referência, como URL em object storage, e o conteúdo pesado fora do broker.