O que é Mensageria Assíncrona e Por Que Importa
Mensageria assíncrona é um padrão arquitetural onde componentes de um sistema se comunicam através de mensagens, sem esperar pela resposta imediata. Em vez de uma chamada síncrona (requisição-resposta), um serviço envia uma mensagem a uma fila e continua sua execução. Outro serviço processa essa mensagem quando estiver pronto. Isso desacopla completamente os sistemas, permitindo escalabilidade, resiliência e melhor distribuição de carga.
Na prática, você utiliza mensageria quando precisa processar tarefas pesadas em background, sincronizar dados entre microserviços, ou lidar com picos de tráfego sem sobrecarregar um único servidor. A diferença com chamadas síncronas é clara: se um serviço falha em uma arquitetura síncrona, tudo cai; em assíncrona, a mensagem fica na fila aguardando retry.
Arquitetura e Padrões Principais
Produtor, Fila e Consumidor
O modelo fundamental é simples: um Produtor coloca mensagens em uma Fila (ou Broker), e um ou mais Consumidores as processam. A fila garante entrega e persistência, enquanto produtor e consumidor não precisam conhecer um ao outro. Existem variações: point-to-point (uma mensagem para um consumidor) e publish-subscribe (uma mensagem para múltiplos consumidores).
# Exemplo com RabbitMQ - Produtor
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='pedidos', durable=True)
mensagem = '{"pedido_id": 123, "cliente": "João", "valor": 150.00}'
channel.basic_publish(
exchange='',
routing_key='pedidos',
body=mensagem,
properties=pika.BasicProperties(delivery_mode=2) # mensagem persistente
)
print("Pedido enviado!")
connection.close()
# Consumidor
import pika
import json
def processar_pedido(ch, method, properties, body):
pedido = json.loads(body)
print(f"Processando pedido {pedido['pedido_id']} de {pedido['cliente']}")
# Simulando processamento
salvar_no_banco(pedido)
ch.basic_ack(delivery_tag=method.delivery_tag)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='pedidos', durable=True)
channel.basic_qos(prefetch_count=1) # processa uma de cada vez
channel.basic_consume(queue='pedidos', on_message_callback=processar_pedido)
print('Aguardando mensagens...')
channel.start_consuming()
Dead Letter Queue e Retry
Quando um consumidor falha ao processar uma mensagem, ela deve ser reprocessada. A estratégia mais comum é utilizar Dead Letter Queues (DLQ). Se após N tentativas a mensagem continuar falhando, ela vai para uma DLQ especial onde pode ser investigada ou reprocessada manualmente.
# Configuração com retry automático
import pika
import json
import time
def setup_rabbitmq_with_dlq():
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# Fila principal
channel.exchange_declare(exchange='pedidos_exchange', exchange_type='direct')
channel.queue_declare(
queue='pedidos_principal',
arguments={'x-dead-letter-exchange': 'pedidos_dlx'}
)
# Dead Letter Exchange
channel.exchange_declare(exchange='pedidos_dlx', exchange_type='direct')
channel.queue_declare(queue='pedidos_dlq')
channel.queue_bind(exchange='pedidos_dlx', queue='pedidos_dlq', routing_key='pedidos')
return channel
def consumidor_com_retry():
channel = setup_rabbitmq_with_retry()
tentativas = 0
def callback(ch, method, properties, body):
nonlocal tentativas
try:
pedido = json.loads(body)
# Simula erro aleatório
if pedido['valor'] < 50:
raise ValueError("Valor mínimo não atingido")
print(f"Pedido processado: {pedido['pedido_id']}")
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
tentativas += 1
if tentativas < 3:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
time.sleep(2 ** tentativas) # backoff exponencial
else:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
channel.basic_consume(queue='pedidos_principal', on_message_callback=callback)
channel.start_consuming()
Ferramentas Populares e Quando Usar
RabbitMQ vs Apache Kafka
RabbitMQ é um message broker tradicional, excelente para arquiteturas de fila. Oferece roteamento sofisticado, TTL de mensagens e DLQ nativas. Use quando você precisa garantias fortes de entrega e baixa latência.
Apache Kafka é um event streaming platform focado em volume. Mantém histórico de mensagens, permite múltiplas leituras da mesma mensagem, e é ideal para data pipelines e event sourcing. Use quando precisa reprocessar histórico ou múltiplos consumidores independentes.
# Exemplo com Kafka - Produtor
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=['localhost:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
evento = {'tipo': 'pedido_criado', 'pedido_id': 123, 'timestamp': '2024-01-15'}
future = producer.send('eventos_pedidos', evento)
try:
record_metadata = future.get(timeout=10)
print(f"Enviado para tópico {record_metadata.topic}")
except Exception as e:
print(f"Erro: {e}")
producer.close()
# Consumidor Kafka
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
'eventos_pedidos',
bootstrap_servers=['localhost:9092'],
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
group_id='grupo_processamento_pedidos',
auto_offset_reset='earliest'
)
for message in consumer:
evento = message.value
print(f"Processando evento: {evento['tipo']} - Pedido {evento['pedido_id']}")
# Sua lógica aqui
Boas Práticas e Armadilhas Comuns
Idempotência é crítica: uma mensagem pode ser entregue mais de uma vez. Seu consumidor deve ser idempotente, ou seja, processar a mesma mensagem múltiplas vezes deve produzir o mesmo resultado. Use IDs únicos e verificações no banco de dados.
Monitore suas filas: acúmulo de mensagens é sinal de que consumidores estão lentos ou falhando. Implemente alertas para profundidade de fila e tempo de processamento.
Evite acoplamento nos schemas: versionize suas mensagens e use campos opcionais. Se um consumidor não entende um campo novo, não deve quebrar.
# Exemplo de processamento idempotente
import hashlib
def processar_pedido_idempotente(pedido, db):
# Gera ID único baseado no conteúdo
pedido_hash = hashlib.md5(
json.dumps(pedido, sort_keys=True).encode()
).hexdigest()
# Verifica se já foi processado
if db.query("SELECT * FROM pedidos WHERE hash = ?", pedido_hash):
print("Pedido já processado, ignorando duplicata")
return
# Processa e registra
db.execute("INSERT INTO pedidos (hash, conteudo) VALUES (?, ?)",
pedido_hash, json.dumps(pedido))
print(f"Pedido {pedido['id']} processado com sucesso")
Conclusão
Mensageria assíncrona é fundamental em sistemas modernos. Os três pilares principais que você deve dominar são: (1) desacoplamento de componentes através de produtores e consumidores independentes, (2) confiabilidade garantida via DLQs, retries e idempotência, e (3) escolha da ferramenta correta — RabbitMQ para filas tradicionais ou Kafka para event streaming. Comece com RabbitMQ se é novo no tema; quando precisar de histórico e múltiplos consumidores, migre para Kafka.