Dead Letter Channel no Mule: a dead letter queue do Anypoint MQ
- 11 min de leitura
Nesta página
- A mensagem de nota fiscal que não pode sumir nem voltar para sempre
- No livro, quem tira a mensagem do caminho é o sistema de mensagens
- O desenho do padrão
- No Anypoint MQ, a DLQ é da fila e o flow só confirma ou não
- A DLQ fica na configuração da fila
- O que fica com o consumidor: ACK ou NACK
- O caminho da mensagem com a DLQ
- Dead Letter Channel ou Invalid Message Channel
- Cuidados com a DLQ
- Quando a DLQ não resolve
Esta página é para quem consome filas do Anypoint MQ num flow Mule e precisa decidir o destino da mensagem que falha sempre. Ela mostra o padrão Dead Letter Channel, como a dead letter queue (DLQ) do Anypoint MQ o implementa e o que fica com o broker e o que fica com a aplicação. O cenário é o da série: um pedido que passa por pagamento, estoque e nota fiscal.
A mensagem de nota fiscal que não pode sumir nem voltar para sempre
O livro Enterprise Integration Patterns parte de uma pergunta: o que o sistema de mensagens faz com uma mensagem que não consegue entregar (Dead Letter Channel)?
No cenário de pedidos, o consumidor de nota fiscal lê o evento de pedido criado e calcula os impostos antes de emitir a nota. Com o serviço de impostos fora do ar, ele não consegue processar a mensagem.
As duas saídas óbvias são ruins. Se o consumidor confirma e segue, o pedido se perde: a nota nunca sai, e ninguém fica sabendo. Devolver a mensagem à fila sem limite faz o consumidor tentar a mesma mensagem de novo e de novo, até o tempo de vida (TTL) das mensagens, configurado na fila, acabar e o Anypoint MQ apagá-la (filas). A mensagem precisa sair do caminho e continuar guardada em algum lugar.
No livro, quem tira a mensagem do caminho é o sistema de mensagens
Quando o sistema de mensagens conclui que não pode ou não deve entregar uma mensagem, ele pode movê-la para um Dead Letter Channel. O livro avisa que o jeito como esse canal funciona depende da implementação de cada sistema de mensagens, se ele tiver um, e que o canal também aparece com o nome de dead message queue ou dead letter queue (DLQ). Ao mover a mensagem, o sistema pode registrar o canal em que ela deveria ter sido entregue (Dead Letter Channel). Quem move a mensagem é o sistema de mensagens, não o receptor, e é isso que separa este padrão do Invalid Message Channel, mais abaixo.
O desenho do padrão
O diagrama redesenha o sketch da página do padrão e mostra quem tira a mensagem do caminho: o remetente envia pelo canal, a entrega ao receptor pretendido falha, e o sistema de mensagens redireciona a mensagem morta (dead message) para o Dead Letter Channel.
No Anypoint MQ, a DLQ é da fila e o flow só confirma ou não
No Anypoint MQ, uma DLQ é uma fila comum que só recebe mensagens não entregues. Ela é atribuída na configuração da fila de origem, e quem move a mensagem é o broker (filas). Neste desenho, a aplicação Mule não escreve na DLQ: ela só decide se confirma (ACK) ou não confirma (NACK) cada mensagem.
A ponte com o livro está na contagem. No Anypoint MQ, um NACK conta como uma tentativa de entrega sem sucesso, e o fluxograma da documentação devolve à fila toda entrega não confirmada e confere o limite de tentativas antes de mover a mensagem para a DLQ (filas). Por isso, a mensagem que o consumidor não consegue processar é, para o broker, o que o livro chama de mensagem que não pode ser entregue.
Por isso esta página não tem exemplo executável no repositório mulesoft-patterns-examples: a configuração e o flow abaixo seguem a documentação e precisam de um broker Anypoint MQ.
A DLQ fica na configuração da fila
Em Anypoint Platform > MQ > Destinations, a configuração tem dois passos (filas):
- Crie primeiro a fila que vai servir de DLQ,
order-created-invoice-dlq. Ela precisa ser do mesmo tipo da fila de origem (standard ou FIFO), estar na mesma região e no mesmo ambiente e pertencer à mesma conta. - Na fila de nota fiscal,
order-created-invoice, ligue Assign a Dead Letter Queue, escolhaorder-created-invoice-dlqem Dead Letter Queue Name e, se quiser, ajuste Delivery attempts before reroute: quantas vezes o Anypoint MQ tenta entregar a mensagem antes de movê-la para a DLQ. O padrão é 10, e o valor vai de 1 a 1000.
A Admin API do Anypoint MQ também cria e gerencia filas (APIs do Anypoint MQ). Na configuração que ela devolve, a fila de nota fiscal fica assim (trecho):
{
"type": "queue",
"queueId": "order-created-invoice",
"fifo": false,
"maxDeliveries": 10,
"deadLetterQueueId": "order-created-invoice-dlq"
}
E a DLQ passa a listar as filas de origem no campo deadLetterSources (filas):
{
"type": "queue",
"queueId": "order-created-invoice-dlq",
"deadLetterSources": ["order-created-invoice"]
}
Não confunda Delivery attempts before reroute com o Max Redelivery Count da aba Redelivery do conector: esse é um parâmetro da política de redelivery do Mule, que lança MULE:REDELIVERY_EXHAUSTED quando a mesma mensagem passa do limite de reentregas ao flow (filas, redelivery policy).
O que fica com o consumidor: ACK ou NACK
O Subscriber do conector usa por padrão o modo de confirmação AUTO: só faz o ACK, que remove a mensagem da fila, se o flow terminar sem erro. Se o flow terminar com um erro propagado, a mensagem recebe um NACK e volta para a fila. Se o flow não terminar dentro do acknowledgementTimeout (dois minutos, quando não configurado), ela também volta (Subscriber, ACK e NACK). O Subscriber também usa por padrão a estratégia prefetch, que guarda mensagens num buffer local; com ela, o Anypoint MQ tenta respeitar o acknowledgementTimeout, mas estende o prazo da mensagem que passa 80% dele no buffer, e a documentação recomenda não usar prefetch quando o prazo precisa ser respeitado (Subscriber, ACK e NACK).
Os outros dois modos mudam essa conta. No MANUAL, o próprio flow chama as operações ACK ou NACK com o ackToken da mensagem. No IMMEDIATE, a mensagem é confirmada e removida da fila antes do processamento e não volta se o processamento falhar. Para não perder mensagens nesse modo, a documentação recomenda uma DLQ (ACK e NACK), mas o broker não tem mais o que mover: a mensagem já saiu da fila, e quem a publicaria na DLQ seria o próprio flow, no tratamento de erro.
Com o modo padrão, o consumidor fica assim; issue-invoice é o flow que chama o serviço de impostos e emite a nota.
<!-- Configuração de referência -->
<!-- ${secure::...} exige um <secure-properties:config> na aplicação -->
<anypoint-mq:config name="Anypoint_MQ_Config">
<anypoint-mq:connection url="${anypoint.mq.url}"
clientId="${anypoint.mq.clientId}"
clientSecret="${secure::anypoint.mq.clientSecret}"/>
</anypoint-mq:config>
<flow name="invoice-consumer">
<anypoint-mq:subscriber config-ref="Anypoint_MQ_Config" destination="order-created-invoice"/>
<flow-ref name="issue-invoice"/>
<error-handler>
<on-error-propagate type="ANY">
<logger level="WARN" message="#['Nota fiscal não emitida; a mensagem volta para a fila. correlationId: ' ++ correlationId]"/>
</on-error-propagate>
</error-handler>
</flow>
O on-error-propagate só registra a falha; é o erro propagado que faz o Subscriber responder com NACK (On Error Propagate). Nenhuma linha manda a mensagem para a DLQ: quem conta as tentativas e move a mensagem é o broker.
O caminho da mensagem com a DLQ
O diagrama mostra o que a aplicação decide e o que o broker faz depois com uma mensagem da fila de nota fiscal.
Dead Letter Channel ou Invalid Message Channel
O livro tem um padrão vizinho para outro problema: a mensagem que chega, mas não faz sentido para o receptor. No Invalid Message Channel, o próprio receptor move a mensagem para um canal especial de mensagens que ele não conseguiu processar (Invalid Message Channel).
Um pedido sem o CPF ou CNPJ do cliente vai falhar nas 10 tentativas do mesmo jeito e chegar à DLQ misturado com os pedidos que só falharam porque o serviço de impostos estava fora do ar. Quando o flow sabe que a mensagem nunca vai ser válida, ele mesmo a manda para uma fila separada:
<!-- Configuração de referência -->
<flow name="invoice-consumer">
<anypoint-mq:subscriber config-ref="Anypoint_MQ_Config" destination="order-created-invoice"/>
<choice>
<when expression="#[isEmpty(payload.customer.taxId)]">
<raise-error type="INVOICE:INVALID_ORDER" description="Pedido sem CPF ou CNPJ do cliente"/>
</when>
</choice>
<flow-ref name="issue-invoice"/>
<error-handler>
<on-error-continue type="INVOICE:INVALID_ORDER">
<anypoint-mq:publish config-ref="Anypoint_MQ_Config" destination="order-created-invoice-invalid"/>
</on-error-continue>
<on-error-propagate type="ANY">
<logger level="WARN" message="#['Nota fiscal não emitida; a mensagem volta para a fila. correlationId: ' ++ correlationId]"/>
</on-error-propagate>
</error-handler>
</flow>
O on-error-continue faz o flow terminar como se tivesse dado certo (On Error Continue), e com isso o modo AUTO confirma a mensagem na fila de nota fiscal depois que ela foi publicada em order-created-invoice-invalid (Raise Error, Publish). A fila de inválidas fica separada da DLQ porque a causa é outra: na DLQ, a mensagem pode voltar à fila depois que o serviço de impostos se recuperar; na fila de inválidas, o pedido precisa ser corrigido na origem, porque reenviá-lo como está daria o mesmo erro.
Cuidados com a DLQ
- FIFO e DLQ não garantem a ordem. A DLQ de uma fila FIFO precisa ser FIFO, e mesmo assim a ordem se perde: a mensagem que vai para a DLQ sai da fila, e as seguintes são processadas sem ela. Se a ordem precisa ser estrita, a documentação diz para não atribuir DLQ à fila FIFO (filas).
- A DLQ precisa de dono e de um caminho de volta. As mensagens da DLQ servem para descobrir por que não foram entregues, e, para recuperá-las, a documentação indica a API REST: ler da DLQ e escrever numa fila (filas). Alguém precisa saber que a DLQ recebeu mensagens e ter um procedimento para devolvê-las depois de corrigir a causa.
- Olhar a fila de origem gasta tentativas. Numa fila com DLQ, ver mensagens pelo Message Browser conta como entrega, e devolvê-las à fila conta como NACK, uma tentativa sem sucesso (filas).
- A DLQ também tem TTL. Como em qualquer fila, o TTL das mensagens (Message TTL), configurado na fila, vai até duas semanas (filas). Numa DLQ que ninguém olha, a mensagem é apagada quando o TTL da DLQ acabar.
- Uma queda do serviço de impostos manda mensagens válidas para a DLQ. Com o serviço fora do ar, todas as mensagens falham e podem esgotar as tentativas. Para esse caso existe o circuit breaker do Subscriber, desligado por padrão e configurado com
<anypoint-mq:circuit-breaker>: depois deerrorsThresholderros seguidos, ele para de consumir durante otripTimeout. Ele reduz as tentativas gastas, mas não as elimina. Com o circuito aberto, as mensagens já consumidas recebem NACK e voltam para a fila; passado otripTimeout, uma única mensagem testa o serviço e, se falhar, o circuito abre de novo (Subscriber). Como cada NACK conta como tentativa sem sucesso (filas), a inferência é que esses NACKs e os testes que falham também gastam tentativas. - Reprocessar pede um receptor idempotente. As filas standard entregam pelo menos uma vez, e às vezes mais de uma (filas). Uma mensagem que volta da DLQ pode encontrar parte do trabalho já feito, e o consumidor precisa produzir o mesmo efeito recebendo uma ou duas vezes, como mostra a página do idempotent receiver.
Quando a DLQ não resolve
A DLQ existe porque há uma fila entre quem envia e quem processa. Pelo mesmo raciocínio, ela não serve a um flow com HTTP Listener, em que não há fila entre quem chama e o flow: quem chamou está esperando a resposta, e o erro volta para ele na hora, com o status e o corpo que o contrato da API define. Não há mensagem guardada para o broker mover, e reenviar a requisição é decisão de quem chamou.
A DLQ também não substitui o tratamento de um erro que o flow sabe resolver. A mensagem que nunca vai ser válida vai para a fila de inválidas. Uma falha curta, que uma nova tentativa logo depois resolveria, pode ser repetida no próprio flow com o Until Successful antes de o erro ser propagado. As tentativas, somadas, precisam caber no acknowledgementTimeout: se o prazo acabar com o flow ainda tentando, a mensagem volta para a fila e pode ser entregue de novo, a este ou a outro consumidor, enquanto a primeira execução continua (ACK e NACK), e essa volta gasta uma tentativa de entrega (filas). A DLQ fica para o que sobra depois disso: a mensagem que o consumidor tentou processar e não conseguiu, e que alguém vai precisar olhar.
Os outros padrões da série, com o componente do Mule que implementa cada um, estão no catálogo de padrões de integração no Mule.