Fundamentos do RabbitMQ
O RabbitMQ é um dos brokers de mensagens mais difundidos no mercado, fundamentado no protocolo AMQP (Advanced Message Queuing Protocol). Ele atua como um intermediário eficiente para comunicação assíncrona, oferecendo escalabilidade e alta disponibilidade. Seus componentes básicos são:
- Broker: O servidor de mensageria que hospeda as filas e as trocas.
- Exchange: O roteadro que recebe mensagens dos produtores e as encaminha para as filas baseando-se em regras.
- Queue: O buffer que armazena as mensagens até que sejam processadas.
- Binding: O elo de ligação entre uma Exchange e uma Queue.
- Producer: A aplicação que envia dados para o broker.
- Consumer: A aplicação que recupera e processa os dados da fila.
Padrões de Roteamento e Modelos de Mensageria
O comportamento do RabbitMQ é definido principalmente pelo tipo de Exchange utilizado. Abaixo, detalhamos os cinco modelos principais:
1. Fila Simples (Simple Queue)
Um produtor envia uma mensagem para uma fila específica, e um único consumidor a processa. É a forma mais direta de comunicação ponto a ponto.
// Produtor Simples
@Service
public class ServicoEnvio {
@Autowired
private RabbitTemplate template;
public void enviarParaFila(String texto) {
template.convertAndSend("fila_comum", texto);
}
}
// Consumidor
@Component
public class ReceptorSimples {
@RabbitListener(queues = "fila_comum")
public void processar(String conteudo) {
System.out.println("Conteúdo recebido: " + conteudo);
}
}
2. Filas de Trabalho (Work Queues)
Utilizado para distribuir tarefas pesadas entre vários consumidores. Por padrão, o RabbitMQ usa o rodízio (round-robin). Para evitar que um consumidor fique sobrecarregado enquanto outro está ocioso, utiliza-se o Fair Dispatch configurando o prefetch.
# Configuração no application.yml para distribuição justa
spring:
rabbitmq:
listener:
simple:
prefetch: 1
3. Publish/Subscribe (Fanout Exchange)
A mensagem é replicada para todas as filas vinculadas à Exchange, independentemente de chaves de rotemaento. Ideal para notificações globais.
@Bean
public FanoutExchange exchangeBroadcast() {
return new FanoutExchange("br.com.notificacao.global");
}
public void dispararBroadcast(String aviso) {
template.convertAndSend("br.com.notificacao.global", "", aviso);
}
4. Roteamento Direto (Direct Exchange)
A mensagem é entregue apenas às filas cujo Routing Key coincide exatamente com o definido no envio. Comumente usado para separar logs por níveis (ERROR, INFO, DEBUG).
5. Tópicos (Topic Exchange)
Permite roteamento flexível baseado em padrões. Utiliza caracteres curingas: * (substitui exatamente uma palavra) e # (substitui zero ou mais palavras).
// Exemplo de roteamento por tópicos: "vendas.nacional.#"
@RabbitListener(queues = "fila_vendas_brasil")
public void tratarVendasBR(String payload) {
// Processa qualquer mensagem com routing key começando por vendas.nacional
}
Garantindo a Confiabilidade da Entrega
Para evitar a perda de dados em sistemas críticos, é necessário atuar em três frentes:
Confirmação do Produtor (Publisher Confirms)
O produtor deve receber um sinal de "recebido" do Broker. No Spring Boot, habilita-se o publisher-confirm-type: correlated.
@PostConstruct
public void configurarConfirmacoes() {
template.setConfirmCallback((dados, sucesso, erro) -> {
if (sucesso) {
log.info("Mensagem aceita pelo Broker");
} else {
log.error("Falha no Broker: {}", erro);
}
});
}
Persistência de Dados
Mensagens e filas devem ser marcadas como duráveis. Caso o Broker reinicie, os dados não serão perdidos se estiverem no disco.
@Bean
public Queue filaPersistente() {
return new Queue("fila_critica", true); // true = durable
}
Confirmação Manual do Consumidor (Manual ACK)
O Broker só remove a mensagem da fila após o consumidor enviar um comando de basicAck. Se o consumidor falhar antes disso, a mensagem volta para a fila.
@RabbitListener(queues = "fila_segura")
public void receber(Message msg, Channel canal) throws IOException {
try {
// Lógica de negócio aqui
canal.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// Reenfileira a mensagem em caso de erro
canal.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
}
}
Estratégias para Idempotência
O processamento duplicado pode ocorrer se o ACK falhar devido a instabilidades de rede. Para mitigar isso, deve-se implementar a idempotência no consumidor, geralmente utilizando uma base de dados ou Redis para rastrear IDs únicos de mensagens.
public void processarComIdempotencia(Pedido pedido) {
String idMensagem = pedido.getUuid();
if (redis.hasKey("msg_processada:" + idMensagem)) {
return; // Já processado, ignorar
}
executarOperacao(pedido);
redis.set("msg_processada:" + idMensagem, "ok", Duration.ofDays(1));
}
Ordenação de Mensagens
O RabbitMQ não garante ordem global se houver múltiplos consumidores ou filas. Para garantir a ordem cronológica, a solução mais comum é limitar a fila a um único consumidor (concurrency = 1) ou garantir que mensagens relacionadas (mesmo ID de cliente, por exemplo) caiam sempre no mesmo shard/fila através de uma lógica de roteamento específica.
Fila de Mensagens Mortas (Dead Letter Exchange - DLX)
Uma mensagem é encaminhada para a DLX quando:
- É rejeitada pelo consumidor (nack/reject) com
requeue=false. - O tempo de vida (TTL) expira.
- O limite de capacidade da fila é atingido.
@Bean
public Queue filaPrincipal() {
Map<String, Object> argumentos = new HashMap<>();
argumentos.put("x-dead-letter-exchange", "exchange_falhas");
argumentos.put("x-dead-letter-routing-key", "chave_dlq");
return new Queue("fila_principal", true, false, false, argumentos);
}
Monitoramento e Métricas
O plugin rabbitmq_management oferece uma interface web essencial para observar o estado do sistema. Através dele, monitoramos:
| Métrica | Importância |
|---|---|
| Message Rates | Verifica o throughput de entrada e saída. |
| Unacknowledged Messages | Indica consumidores travados ou lentos. |
| Memory/Disk Alarms | Previne interrupção do serviço por falta de recursos. |
Para habilitar, utiliza-se o comando: rabbitmq-plugins enable rabbitmq_management.