Mensageria Assíncrona na Prática Já leu

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

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.

Referências


Artigos relacionados