Por: @felipefc, @mariliars
Publicado em: 2018-07-27
Arquitetura das filas de retry
Uma das formas de comunicação entre os nossos serviços é através de um servidor de mensageria: o RabbitMQ. A troca de mensagens é implementada como uma lógica de publishers e subscribers. Por exemplo, um cliente é apagado e o serviço que gerencia os clientes publica uma mensagem "clients.deleted"
. Os recursos que estão ligados aos clientes estarão “ouvindo” especificamente esse tópico e executarão uma ação como consequência. Por exemplo, apagar todos os certificados SSL vinculados ao cliente recém apagado.
O problema
A arquitetura era básica, mensagens eram publicadas por um serviço, recebidas e processadas por outro. Porém, durante a implementação do processamento de logs do VPNFlex um problema foi identificado: o que acontece quando houver um erro durante o recebimento ou processamento da mensagem? A resposta era simples: a mensagem seria perdida. No entanto, naquela ocasião as mensagens de logs não poderiam ser simplesmente descartadas, visto que a consistência dos relatórios ficaria prejudicada.
A primeira solução pensada foi utilizar um parâmetro próprio da biblioteca AMQP utilizada que reenfileira as mensagens até receber um ack
(certificando o recebimento da mensagem). No entanto, com este atributo as mensagens eram reenviadas ilimitadamente até serem processadas. O impacto disso era o enchimento da memória e, em casos extremos, a parada do serviço RabbitMQ. Era preciso pensar em uma solução mais elaborada, onde as mensagens pudessem aguardar o reprocessamento por mais tempo e com limite de tentativas.
Solução
A solução implementada foi montar uma arquitetura personalizada, utilizando dos recursos disponibilizados pelo RabbitMQ: exchanges e filas; e alguns parâmetros como: TTL (tempo de vida da mensagem) e Dead Letter Exchange.
TTL: Time-To-Live
Em nosso cenário este parâmetro foi aplicado sobre as filas de espera. O TTL define qual o intervalo de tempo que uma mensagem aguarda até ser consumida e, se não for, é encaminhada para uma outra Exchange.
DLX: Dead Letter Exchange
No RabbitMQ, quando uma mensagem é rejeitada ou ocorre um evento de erro, ela pode ser simplesmente descartada. No entanto, ao declarar uma Dead Letter Exchange, a mensagem é republicada nessa Exchange e poderá seguir um processamento alternativo ao invés de ser ignorada.
Retomando a construção da nossa arquitetura de filas de Retry, tínhamos como objetivo que as mensagens não processadas deveriam tomar a seguinte sequência de passos:
- A mensagem é publicada pelo
SERVICE_1
- A mensagem é capturada pelo
SERVICE_2
- Durante o processamento da mensagem ocorre uma
Exception
. Dessa forma, o tratamento da mensagem não é completado. - Nesse momento, o atributo
retry_count
(contido no cabeçalho da mensagem) é incrementado. A partir do valor que ele possui é determinado para qual fila de Retry a mensagem será repassada. Como neste exemplo é a primeira tentativa, a mensagem ficará em espera por 1 minuto. - Passado 1 minuto a mensagem volta para a fila de consumo do
SERVICE_2
até ser reprocessada. - Novamente uma
Exception
durante o processamento. E agora? - A mensagem é novamente repassada para uma fila de espera, porém agora aguardará por 15 minutos antes de realizar uma nova tentativa.
A ideia principal gira em torno de várias tentativas de processamento, com intervalos de tempo prédefinidos. A quantidade de tentativas e os tempos são configuráveis em código.
Por exemplo:
RETRY_QUEUES = [
"10s", # 10 segundos
"30s", # 30 segundos
"5m", # 5 minutos
]
A partir dos intervalos definidos acima e construída a aplicação, a arquitetura de filas e exchanges assumiria esta forma:
Basicamente, após expiradas as três tentativas e, mesmo assim a mensagem não for corretamente processada, ela enfim será descartada. Ao determinar longos intervalos de espera pelo reprocessamento, é aberta a possibilidade de correção dos bugs causadores nesse meio tempo, para que na próxima tentativa a mensagem possa ser processada.
Concluindo, é importante ressaltar que esta arquitetura é montada para todos os serviços. Sendo assim, cada serviço dos produtos iTFLEX possui:
- Uma Dead Letter Exchange
DLX_<SERVICE_NAME>
- Uma Exchange para encaminhamento da tentativa seguinte
RETRY_<SERVICE_NAME>
- Três (dependendo da configuração) filas de Retry
RETRY.1m-<SERVICE_NAME>``RETRY.15m-<SERVICE_NAME>``RETRY.1h-<SERVICE_NAME>