Marcio Cunha

Como criar arquiteturas orientadas a eventos resilientes

Descubra como projetar sistemas orientados a eventos usando Kafka e RabbitMQ com garantia de entrega at-least-once e isolamento de mensagens venenosas.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • Sistemas orientados a eventos desacoplam microsserviços por meio de mensagens assíncronas publicadas em barramentos.
  • A garantia at-least-once exige confirmações manuais e reprocessamento controlado para evitar perda de dados.
  • Mensagens venenosas travam consumidores em loops infinitos caso não sejam isoladas em filas de DLQ dedicadas.
  • Apache Kafka gerencia o fluxo por meio de logs imutáveis e ponteiros de leitura controlados por cada consumidor.
  • RabbitMQ utiliza confirmações explícitas de entrega e roteamento flexível por trocadores para lidar com alta concorrência.

Por que sistemas baseados em eventos precisam de resiliência

Quando construímos aplicações modernas, costumamos dividir as responsabilidades em pequenos blocos que conversam entre si enviando recados. Em vez de uma tela chamar diretamente o banco de dados para salvar um pedido, ela simplesmente avisa ao mundo que 'um pedido novo nasceu' e segue a vida. Esse aviso é o que chamamos de evento. No entanto, o mundo real dos computadores é caótico: redes caem, servidores reiniciam no meio da madrugada e bancos de dados sofrem picos repentinos de lentidão. É exatamente por isso que precisamos planejar a resiliência desde o primeiro rascunho da arquitetura.

Na prática, isso significa que a nossa infraestrutura de mensageria não pode simplesmente fingir que nada aconteceu quando um erro aparece. Se um sistema intermediário falha ao processar a criação de um usuário, a mensagem não pode evaporar no ar. Precisamos garantir que cada bit de informação chegue ao seu destino final, mesmo que o caminho exija novas tentativas. Para entender como proteger esses fluxos críticos, vale a pena relembrar estratégias complementares de proteção de infraestrutura, como como blindar APIs REST contra abuso de requisições com Redis, que ajudam a conter o caos antes mesmo que ele vire uma mensagem na fila.

Garantia de entrega at-least-once sem duplicar dados

Um dos maiores debates na engenharia de software distribuído gira em torno de como entregar mensagens sem perdê-las. Existem basicamente três filosofias: no-at-least-once (onde mensagens podem sumir), at-most-once (onde mensagens chegam no máximo uma vez, correndo risco de sumir) e at-least-once (pelo menos uma vez). Na abordagem at-least-once, o barramento insiste em reenviar o dado até que o consumidor responda com um recibo de leitura válido. O problema colateral dessa insistência é a duplicação: se o recibo se perde no caminho de volta, o servidor envia o mesmo aviso de novo.

Para blindar o sistema contra essa duplicidade, adotamos o conceito de idempotência. Na prática, idempotência significa desenhar a operação para que executá-la dez vezes tenha exatamente o mesmo efeito de executá-la apenas uma vez. Se o sistema recebe duas vezes o evento de pagamento aprovado para o boleto número 123, a lógica interna verifica que o pagamento já foi registrado e simplesmente ignora a segunda chamada. Assim, ganhamos a segurança de que nenhuma mensagem ficou para trás sem o risco de corromper o saldo dos clientes por repetição.

Dominando o tratamento de mensagens venenosas

Mesmo com toda a cautela do mundo, em algum momento uma mensagem corrompida ou malformada vai aterrissar no seu sistema. Chamamos essa anomalia de mensagem venenosa (ou poison pill). Trata-se de um pacote de dados que, por conter um formato inválido ou acionar um erro inesperado de código, faz o servidor travar ou entrar em um loop infinito de tentativas de reprocessamento. Quando isso acontece, a fila inteira congela porque o consumidor gasta todo o seu tempo tentando processar o mesmo item defeituoso.

A solução padrão de mercado para esse pesadelo é o uso de filas de cartas mortas, conhecidas no jargão técnico como Dead Letter Queues (DLQ). O mecanismo funciona como uma área de quarentena. Configuramos o barramento para contar quantas vezes uma mensagem falhou. Se ultrapassar um limite seguro, digamos três tentativas, a mensagem venenosa é retirada do fluxo principal e jogada diretamente na DLQ. Lá, ela fica guardada em segurança para inspeção humana, permitindo que a aplicação continue processando o restante do fluxo normal sem interrupções.

Desenhando arquiteturas robustas com Apache Kafka

O Apache Kafka é uma ferramenta poderosa quando precisamos lidar com um volume colossal de dados fluindo em tempo real. Diferente de filas tradicionais que deletam a mensagem assim que ela é lida, o Kafka funciona como um grande livro de registros imutável. Cada consumidor mantém o controle da sua própria página de leitura através de um ponteiro numérico chamado offset. Isso significa que, se um servidor falhar, ele pode simplesmente voltar algumas linhas no livro de registros e retomar o trabalho exatamente de onde parou, sem perda de dados.

Para garantir a entrega at-least-once no Kafka, ajustamos as configurações para que o consumidor só avance o seu ponteiro de leitura após confirmar que a regra de negócio terminou com sucesso. Se a aplicação cai no meio do caminho, o ponteiro não é atualizado. Quando o serviço volta à vida, o Kafka reenvia todas as mensagens desde a última marcação segura. Combinar essa resiliência de log com ferramentas modernas de automação e redundância de infraestrutura, como como automatizar backups seguros no Proxmox com snapshots e ZFS, garante que você recupere tanto os dados em trânsito quanto o estado dos seus servidores.

Orquestrando fluxos e garantias no RabbitMQ

Enquanto o Kafka se apoia na filosofia de logs e reexecução, o RabbitMQ brilha na entrega tradicional de mensagens baseada em filas e roteamento inteligente. Ele utiliza trocadores, conhecidos como exchanges, para espalhar os avisos para diferentes filas de acordo com regras específicas de roteamento. Para garantir a confiabilidade nesse ecossistema, usamos o mecanismo de confirmação explícita de entrega, onde o consumidor precisa enviar um sinal de reconhecimento claro para o servidor de mensageria.

No RabbitMQ, se a conexão cai antes de o consumidor mandar esse sinal, o servidor entende que a tarefa falhou e devolve a mensagem imediatamente para a fila, ficando pronta para ser entregue a outro trabalhador disponível. Em conjunto com políticas bem configuradas de x-max-priority e x-dead-letter-exchange, conseguimos estruturar fluxos altamente tolerantes a falhas. Dessa forma, mesmo diante de quedas repentinas de rede, a aplicação mantém o ritmo operacional sem corromper o estado dos dados.

Conclusão e próximos passos na engenharia resiliente

Desenhar arquiteturas orientadas a eventos exige abandonar a ilusão de que a rede é sempre estável e os serviços rodam para sempre sem interrupções. Ao adotar o padrão de entrega at-least-once em conjunto com mecanismos firmes de idempotência, garantimos que nenhum dado crítico desapareça no meio do caminho. Além disso, isolar mensagens venenosas em filas de cartas mortas evita que falhas pontuais paralisem operações inteiras de negócio.

Seja escolhendo a robustez de logs imutáveis do Kafka ou a flexibilidade de roteamento do RabbitMQ, o segredo da resiliência reside na antecipação do caos. Implemente políticas claras de retranstentativa, monitore o tamanho das suas DLQs e teste simulações de queda em ambiente de homologação. Com essas práticas consolidadas, sua engenharia estará pronta para absorver picos de tráfego e falhas inesperadas com elegância e estabilidade.