Dead Letter Queue | RabbitMQ | CSharp
- Dead Letter Queue | RabbitMQ | CSharp
Há 3 situações que podem ocorrer de ir para a DLQ
-
Replay automatizado Quando a falha foi transitória, como timeout, indisponibilidade de banco ou erro momentâneo de infraestrutura. A mensagem está válida, então pode ser reprocessada com critério.
-
Repair & re-drive Quando o problema está no payload ou na lógica da aplicação. Antes de reenviar, alguém precisa corrigir o dado ou o sistema, senão a mensagem só morrerá de novo.
-
Expurgo auditado Quando a mensagem não deve mais voltar ao fluxo, como em fraude confirmada ou dado irrecuperável. Em vez de apagar, o correto é arquivar com rastreabilidade para auditoria e compliance.
Por que a DLQ não se remove sozinha no RabbitMQ
-
autoDeleteNÃO depende do serviço parar- Para filas, só remove quando o último consumer desconecta
- DLQ normalmente não tem consumer
-
DLQ sem consumer nunca ativa o auto-delete
- Logo, a fila permanece
- O binding permanece
- A exchange DLX permanece
-
Exchanges auto-delete só somem quando não há bindings
- Parar serviços não remove bindings
- Binding só some quando a fila é deletada
-
Com múltiplas instâncias
- Não pode usar
exclusive - RabbitMQ assume que outro worker pode voltar a usar
- Não pode usar
Regra final
DLQ/DLX são tratadas como infraestrutura persistente, não como recursos temporários de execução.
Por isso não se autodestroem automaticamente.
O que é uma Dead Letter Queue (DLQ)?
Uma Dead Letter Queue (DLQ) é uma fila usada para armazenar mensagens que não puderam ser processadas com sucesso por uma fila principal.
No RabbitMQ, uma mensagem é encaminhada automaticamente para uma DLQ quando:
- ❌ O consumidor rejeita a mensagem (
BasicRejectouBasicNackcomrequeue = false) - ⏱️ A mensagem expira ao atingir o TTL
- 📦 A fila atinge seu limite máximo (
x-max-length) - 🚫 A mensagem não pode ser roteada corretamente
O principal objetivo da DLQ é evitar perda silenciosa de mensagens, permitindo diagnóstico, auditoria e reprocessamento controlado.
Por que usar DLQ no RabbitMQ?
Utilizar DLQ traz benefícios importantes em ambientes produtivos:
- 🔍 Diagnóstico de falhas
- 🧾 Auditoria de mensagens problemáticas
- 🔁 Reprocessamento manual ou automático
- 🛑 Isolamento de mensagens inválidas
- 🧠 Separação de erro transitório vs erro definitivo
Sem DLQ, mensagens rejeitadas com requeue = false são perdidas definitivamente.
Quando uma mensagem vai para a DLQ?
| Situação | Vai para DLQ? |
|---|---|
BasicAck | ❌ Não |
BasicReject(requeue: true) | ❌ Volta para fila |
BasicReject(requeue: false) | ✅ Sim |
BasicNack(requeue: false) | ✅ Sim |
| TTL expirado | ✅ Sim |
Fila cheia (x-max-length) | ✅ Sim |
Como configurar Dead Letter Queue no RabbitMQ com C#
Passo 1: Instalar o pacote
dotnet add package RabbitMQ.Client
Passo 2: Conceito importante (DLX)
A DLQ não recebe mensagens diretamente.
O fluxo real é:
Fila Principal
↓ (erro)
Dead Letter Exchange (DLX)
↓
Dead Letter Queue (DLQ)
👉 Sempre existe um Exchange intermediário (mesmo quando é o exchange default "").
OBS:
No bind
Exemplo Completo e Correto
Criando Dead Letter Exchange e DLQ
channel.ExchangeDeclare(
exchange: "fila.dlx",
type: ExchangeType.Direct,
durable: true);
channel.QueueDeclare(
queue: "fila_principal.dlq",
durable: true,
exclusive: false,
autoDelete: false);
channel.QueueBind(
queue: "fila_principal.dlq",
exchange: "fila.dlx",
routingKey: "fila_principal.erro");
Criando a fila principal com DLQ configurada
channel.QueueDeclare(
queue: "fila_principal",
durable: true,
exclusive: false,
autoDelete: false,
arguments: new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "fila.dlx" },
{ "x-dead-letter-routing-key", "fila_principal.erro" }
});
✔️ Agora qualquer mensagem rejeitada corretamente irá para a DLQ.
Enviando mensagens
channel.BasicPublish(
exchange: "",
routingKey: "fila_principal",
basicProperties: null,
body: Encoding.UTF8.GetBytes("Mensagem de teste"));
Consumidor da fila principal (com ACK/NACK correto)
consumer.Received += async (sender, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
// Simula erro
throw new Exception("Erro de processamento");
channel.BasicAck(ea.DeliveryTag, false);
}
catch
{
// NÃO reprocessa → envia para DLQ
channel.BasicNack(
deliveryTag: ea.DeliveryTag,
multiple: false,
requeue: false);
}
};
⚠️ Nunca relance exceções sem controle, pois isso pode matar o consumer.
Consumindo mensagens da DLQ
consumer.Received += (model, ea) =>
{
var mensagem = Encoding.UTF8.GetString(ea.Body.ToArray());
Console.WriteLine($"Mensagem na DLQ: {mensagem}");
// Ack para não ficar presa na DLQ
channel.BasicAck(ea.DeliveryTag, false);
};
Headers importantes na DLQ (x-death)
O RabbitMQ adiciona automaticamente:
"x-death": [
{
"queue": "fila_principal",
"reason": "rejected",
"count": 1,
"time": "2026-01-06"
}
]
Esses headers permitem:
- Saber quantas vezes falhou
- Saber onde falhou
- Criar lógica de retry controlado
Retry com TTL (Boa prática)
Arquitetura comum:
Fila Principal
↓ erro
Retry Queue (TTL 30s)
↓
Fila Principal
↓ falhou X vezes
DLQ Final
Configuração típica:
x-message-ttlx-dead-letter-exchange
Parâmetros importantes de DLQ
| Parâmetro | Descrição |
|---|---|
x-dead-letter-exchange | Exchange para erro |
x-dead-letter-routing-key | Routing key da DLQ |
x-message-ttl | Tempo máximo da mensagem |
x-max-length | Limite de mensagens |
x-max-length-bytes | Limite em bytes |
Padrão de nomenclatura
| Tipo | Finalidade | Padrão de Nome | Exemplo |
|---|---|---|---|
| Queue (Principal) | Processamento normal de mensagens | <domínio>.<serviço>.<ação>.queue | pedido.pagamento.autorizar.queue |
| Queue (DLQ) | Mensagens rejeitadas ou expiradas | <fila_principal>.dlq | pedido.pagamento.autorizar.queue.dlq |
| Queue (Retry) | Reprocessamento com atraso | <fila_principal>.retry | pedido.pagamento.autorizar.queue.retry |
| Queue (Retry com TTL) | Retry com tempo definido | <fila_principal>.retry.<ttl> | pedido.pagamento.autorizar.queue.retry.30s |
| Queue (Parking Lot) | Falhas definitivas | <fila_principal>.parking | pedido.pagamento.autorizar.queue.parking |
| Exchange (Principal) | Roteamento de eventos/mensagens | <domínio>.<serviço>.<evento>.exchange | pedido.pagamento.criado.exchange |
| Exchange (DLX) | Exchange de erro | <exchange_principal>.dlx | pedido.pagamento.exchange.dlx |
| Exchange (Retry) | Exchange para retry | <exchange_principal>.retry | pedido.pagamento.exchange.retry |
| Routing Key (Principal) | Roteamento padrão | <domínio>.<serviço>.<ação> | pedido.pagamento.autorizar |
| Routing Key (Erro) | Mensagens com falha | <routing_key>.erro | pedido.pagamento.autorizar.erro |
| Routing Key (Retry) | Mensagens em retry | <routing_key>.retry | pedido.pagamento.autorizar.retry |
| Queue com Ambiente | Isolamento por ambiente | <env>.<fila> | prd.pedido.pagamento.autorizar.queue |
| Queue Versionada | Evolução de schema | <fila>.vN | pedido.pagamento.autorizar.queue.v2 |
Boas práticas em produção
✅ Use DLQ apenas para erros definitivos
✅ Separe retry de erro final
✅ Monitore DLQs (alertas!)
✅ Nunca use autoAck=true em processamento crítico
✅ Sempre dê Ack na DLQ após inspeção
❌ Não use DLQ como fila principal