Arquitetura

Como um Serviços perde mensagens no RabbitMQ – Parte 2

Parte 1

Mecanismos de Alta Disponibilidade

Mirrored Queues

As Mirrored Queues são um mecanismo que permite obter tolerância de partição com o RabbitMQ, pois replica as mensagens das filas para mais nós do cluster.

Abaixo estão descritas alguns aspectos de seu funcionamento:

  • Utiliza Chained Replication Algorithm (topologia de anel – cadeia de mirrors)
  • Erlang Partition Detection é o algorítmo responsável pela descoberta de novas partições.
  • Quando um broker (Nó) cai e sobe novamente, as filas desse nó iniciam vazias.
    • Comando “queues rebalance” pode ser usado para rebalancear as filas manualmente, ou pode ser configurada a replicação automática

Fonte da imagem:

https://www.rabbitmq.com/blog/2020/04/20/rabbitmq-gets-an-ha-upgrade/

A sincronização das mirrored queues pode ser:

  • Manual
    • Param o cluster para fazer a sincronização dos brokers
    • Tornam o serviço indisponível momentaneamente
    • Usadas quando queremos priorizar consistência no lugar de disponibilidade
    • Ideal para filas pequenas, para minimizar o tempo de indisponibilidade
  • Automática
    • Mesmas características da replicação manual, porém, é disparada automaticamente quando há alguma mudança no cluster (queda de um nó, por exemplo)
  • Natural
    • Normalmente usada para filas grandes, somente são replicadas as mensagens novas para novos mirrors, isso pode causar a perda de mensagens antigas que por ventura tenham ficado apenas no nó que estava previamente ativo, caso ele venha a falhar 
    • Usadas quando queremos priorizar disponibilidade no lugar de consistência

Abaixo temos um exemplo de policy onde as filas cujos nomes começam com “two.” são espelhadas para quaisquer 2 nós do cluster, com automatic synchronisation habilitada:

rabbitmqctl set_policy ha-two “^two\.” \  ‘{“ha-mode”:”exactly”,”ha-params”:2,”ha-sync-mode”:”automatic”}’

Fonte do comando: 

https://www.rabbitmq.com/ha.html#interstitial

Quorum Queues

As Quorum Queues são um novo tipo de filas que passará a ser a configuração padrão de HA do RabbitMQ, seu foco é prover maior Data Safety em comparação com as Mirrored Queues.

Abaixo estão algumas características desse mecanismo:

  • Usa Raft Consensus Algorithm (https://raft.github.io) – Mesmo utilizado pelo Consul, Vault
    • Prioriza consistência em detrimento da disponibilidade
  • Não suportam algumas funcionalidades das mirrored queues, como:
    • TTL
    • Mensagens não persistentes
    • Prioridades de mensagens
  • Funcionamento dividido em 2 fases
    • Eleição de líder
      • Assim como nas mirror queues, é necessário eleger um nó Master/Líder ao iniciar o cluster, ou ao perder um nó líder
      • RA dispara as eleições quando identifica a queda de um nó Líder
      • Biblioteca RA é mais rápida para identificar queda de nós ou rede entre os brokers 
    • Replicação de log – ACKs são direcionados para o líder, e não mais para cada broker em forma de anel
      • A estrutura de dados por baixo é um log
      • Baseado em replicação de logs – uma vez que a maioria dos nós possuem a mensagem, então ela pode ser enviada para os consumers
      • A queue é apenas um snapshot instantâneo do command log, o que garante maior segurança sobre a informação armazenada em comparação às mirrored queues
  • Storage
    • Write ahead log – a mensagem é escrita em disco antes de replicar para todos nós
    • A mensagem sempre será escrita uma vez e potencialmente ela será escrita 2 vezes (quando for movida para um segment file)
    • Comparado com Mirrored Queues – onde a mensagem pode nunca ser escritaou escrita apenas 1 única vez no disco, o queé uma das causas da limitação forte entre consistência e disponibilidade
  • Quorum Queue pode ser criada com o argumento x-queue-type igual a quorum
    • Não é possível utilizar polices para transformar uma fila comum em uma Quorum Queue

Exemplos de Perda de Mensagens

Setup do Ambiente

Explicando brevemente a topologia:

  • Com o K6, iremos simular 10 usuários virtuais postando uma mensagem a cada 100ms, durante 30s
    • No total, podem ser geradas até 3000 mensagens, porém, devido aos tempos de resposta da API, será menos que isso
    • Script K6 utilizado em cada cenário:

AutoAck Habilitado – Cenário Feliz

O primeiro cenário é o de sucesso, com AutoAck habilitado, que é o caso onde não ocorre perda de mensagens.

  • Mantivemos o Consumer Service com AutoAck HABILITADO, iniciando o serviço com a seguinte configuração.
  • Mantivemos o Processor Service DISPONÍVEL durante todo tempo, executando a seguinte requisição:

curl –request POST \

–url http://localhost:8081/v1/available \

–header ‘Content-Type: application/json’ \

–data true

  • Executamos o script do K6

cd rabbitmq-producer-service

k6 run script.js

  • O resultado observado é que 100% das mensagens enviadas pelo K6 foram computadas no Processor Service. Verificamos isso com a seguinte requisição:

curl –request GET \

–url http://localhost:8081/v1/processed

AutoAck Habilitado – Cenário de Falha

Agora, vamos executar o mesmo script do K6, porém, simulando uma breve indisponibilidade no Processor Service.

  • Realizamos a limpeza das métricas com as seguintes requisições

curl –request DELETE \

–url http://localhost:8080/v1/consumed

curl –request DELETE \

–url http://localhost:8081/v1/processed

  • Mantivemos o Consumer Service com AutoAck HABILITADO
  • O Processor Service ficará INDISPONÍVEL por alguns instantes, com a seguinte request:

curl –request POST \

–url http://localhost:8081/v1/available \

–header ‘Content-Type: application/json’ \

–data false

  • Após alguns segundos, voltamos a habilitar o Processor Service, com a seguinte request:

curl –request POST \

–url http://localhost:8081/v1/available \

–header ‘Content-Type: application/json’ \

–data true

  • Executamos o script do K6 novamente, que enviou 2710 mensagens para o broker
  • O resultado observado foi que as mensagens consumidas enquanto o Processor Service estava INDISPONÍVEL foram PERDIDAS
  • Apenas 1870 mensagens foram de fato processadas

AutoAck Desabilitado – Cenário de Falha

Para solucionar esse problema, iremos reiniciar o Consumer Service com envio de ACK Manual.

  • Para isso, ajustamos a seguinte variável e reiniciamos o Consumer Service

Observações:

* Garantia de ordem ocorre somente se as mensagens forem publicadas em 1 channel, 1 exchange, 1 queue.

* Em versões anteriores a 2.7.0. a ordem ainda pode ser perdida por ações de requeue

TTL   

O último exemplo de perda de mensagens que reproduzimos é relacionado ao TTL mal configurado.

  • Para isso, deletamos a fila criada no RabbitMQ para recriá-la com outro TTL
  • Após isso, paramos o Consumer Service, ajustamos a seguinte linha de código para ttl=5000 (em milissegundos) e iniciamos novamente o serviço
  • Com os serviços UP novamente, limpamos as métricas, e executamos o K6 novamente
    • Lembre que, já resolvemos a questão do AutoAck, portanto, não teremos mais esse problema
    • Durante a execução, pausamos o serviço Processor Service por uma janela de tempo maior que 5 segundos
    • Com isso, forçamos algumas mensagens a permanecer no broker por um tempo maior do que o TTL
  • Das 2706 mensagens postadas no broker, foram consumidas 9216, ou seja, o RabbitMQ fez seu papel de tentar reenviar as mensagens que não tiveram ACK
  • Mesmo assim, apenas 1785 mensagens foram de fato processadas pelo Processor Service. Isso significa que as mensagens que atingiram seu tempo máximo de vida foram descartadas pelo broker e não serão reenviadas.

 

Conclusão

O RabbitMQ é uma ferramenta bastante poderosa e flexível em termos de protocolos e patterns que ela permite implementar, além de possuir inúmeras bibliotecas em diferentes linguagens de programação que a tornam muito prática em diversos cenários de negócio.

Do ponto de vista de infraestrutura, o RabbitMQ oferece mecanismos para tolerância de partição, o que garante maior durabilidade das mensagens nos nós do cluster. Como toda abordagem de tolerância à partição, teremos sempre que avaliar o tradeoff entre consistência e disponibilidade, selecionando os tipos de filas e tipos de replicação adequados a cada caso.

Conforme observado, a implementação realizada interfere muito na garantia de processamento das mensagens, portanto, essa não é uma responsabilidade exclusiva do Message Broker.

Se sua aplicação não puder se dar ao luxo de perder mensagens, procure utilizar abordagens baseadas no envio manual do sinal de ACK, especialmente se você realiza recebimento de mensagens em lotes. Além disso, procure dimensionar bem suas configurações de TTL e tamanho máximo de filas, para que eventuais indisponibilidades nos seus serviços não causem a perda de mensagens.  Conte com as Dead Letter Queues como uma proteção adicional em casos de longos períodos sem consumo de mensagens.

Esta imagem possuí um atributo alt vazio; O nome do arquivo é arthur-2.png

Referências

1 comentário

Os comentários estão fechados.

%d blogueiros gostam disto: