Escalando RabbitMQ: Um Guia para Otimizar Topologias de Cluster
Projete clusters RabbitMQ que escalam sem confundir clustering, replicação e throughput.
Escalando RabbitMQ: Um Guia para Otimizar Topologias de Cluster
Escalar RabbitMQ começa com um fato desconfortável: um cluster não é um broker mágico maior. É um conjunto de brokers que compartilham metadados e, dependendo do tipo de fila, podem replicar dados da fila. Se uma única fila está sobrecarregada, adicionar nós ao redor pode melhorar a disponibilidade, mas não fará automaticamente com que essa fila consuma mais rápido.
Essa distinção evita muitos designs ruins. Já vi equipes adicionarem dois nós a uma implantação ocupada do RabbitMQ, não moverem nada, não alterarem o layout das filas e se perguntarem por que a mesma fila ainda acumula backlog todas as tardes. O líder da fila ainda estava no mesmo nó. Os mesmos consumidores ainda estavam fazendo o mesmo trabalho. O cluster tinha mais máquinas, mas o gargalo não havia se movido.
A topologia do cluster RabbitMQ trata principalmente de decidir onde as filas vivem, quantas cópias de mensagens importantes você precisa e quanta falha você pode tolerar antes que o throughput caia. A resposta certa para um pipeline de métricas de curta duração não é a mesma que para pagamentos, processamento de pedidos ou eventos de auditoria.
O que o clustering realmente compartilha
Os nós do RabbitMQ em um cluster compartilham definições: hosts virtuais, usuários, permissões, exchanges, filas, bindings, políticas e metadados de tempo de execução necessários para o cluster operar. Um produtor conectado a um nó pode publicar em uma exchange cujo líder da fila está em outro nó. Um consumidor pode se conectar a um nó diferente daquele que hospeda a fila.
Isso não significa que toda mensagem existe em todos os lugares.
Filas clássicas têm um líder em um nó. Filas de quorum têm um líder mais réplicas. Streams têm seu próprio modelo de replicação. Se um líder de fila está remoto da maioria dos clientes que o utilizam, o RabbitMQ precisa mover o tráfego através da interconexão do cluster. Isso é aceitável com moderação. Torna-se caro quando cada publicador se conecta ao nó A, cada fila quente vive no nó B e cada consumidor se conecta ao nó C.
Uma regra simples inicial funciona bem: conecte aplicações aos nós que estão próximos das filas que elas usam, ou coloque um balanceador de carga na frente do cluster e verifique se os líderes das filas estão razoavelmente balanceados. Não presuma que conexões round-robin de clientes criam carga round-robin nas filas.
Prefira três nós antes de ser criativo
Para a maioria dos clusters RabbitMQ de produção, três nós em uma região de baixa latência ou grupo de zonas de disponibilidade é o ponto de partida mais limpo. Isso dá às filas de quorum um modelo de maioria que pode sobreviver a uma falha de nó e mantém a coordenação do cluster simples o suficiente para raciocinar durante um incidente.
Clusters de dois nós parecem mais baratos, mas são complicados para filas replicadas. Com sistemas baseados em quorum, uma maioria é necessária. Se um dos dois nós desaparecer, não há maioria. Você pode adicionar um terceiro nó do tipo testemunha em alguns sistemas distribuídos, mas para RabbitMQ geralmente é mais simples e confiável executar três nós reais com capacidade de disco e rede suficiente.
Cinco nós podem fazer sentido quando você tem muitas filas, precisa de mais opções de posicionamento ou deseja distribuir a carga por mais máquinas. Também aumenta a quantidade de comunicação do cluster e a superfície operacional. Antes de passar de três para cinco, verifique se você está resolvendo a saturação do nó ou um problema de design de fila. Se uma fila está quente, mais nós sozinhos não dividirão o trabalho dessa fila.
Filas de quorum são para confiabilidade replicada, não para velocidade gratuita
Para novas cargas de trabalho de alta disponibilidade, filas de quorum geralmente são o padrão correto quando a durabilidade da mensagem é importante. Elas replicam mensagens usando um protocolo de consenso. Uma fila de quorum com três membros pode continuar operando se um membro estiver indisponível, desde que uma maioria permaneça saudável.
A compensação é o custo de escrita. Uma mensagem persistente publicada precisa ser replicada para membros suficientes antes de ser considerada aceita com segurança. Isso é exatamente o que você deseja para trabalhos importantes, mas não é o mesmo perfil de desempenho de uma fila clássica transitória.
Declare uma fila de quorum com um argumento ou política, dependendo de como sua aplicação gerencia a topologia:
rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'
Para políticas, escope-as cuidadosamente. Não converta acidentalmente todas as filas em um host virtual em filas de quorum apenas porque um padrão amplo correspondeu a .*. Um bom nome de política e um prefixo de fila estreito são chatos da melhor forma:
rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues
Se você está migrando de filas espelhadas clássicas, trate isso como uma migração, não como uma troca de flag. Filas espelhadas clássicas e filas de quorum se comportam de maneira diferente em relação à ordenação, mensagens venenosas, uso de memória e failover. Crie o novo tipo de fila, direcione uma parte controlada do tráfego, observe confirmações e latência do consumidor, depois mova o restante.
Filas clássicas ainda têm seu lugar
Filas clássicas ainda são úteis para cargas de trabalho onde a replicação não é necessária, onde as mensagens são transitórias ou onde a fila é local a um serviço e pode ser reconstruída a partir de outra fonte. Elas também são uma opção razoável para eventos de alto volume e baixo valor onde perder algumas mensagens durante uma falha de nó é aceitável.
Use filas clássicas deliberadamente. Se uma fila clássica é durável e recebe mensagens persistentes, essas mensagens são armazenadas no nó que hospeda essa fila. Se esse nó cair, a fila fica indisponível até que o nó retorne. Isso pode ser aceitável para um trabalho de reconciliação em segundo plano. Geralmente não é aceitável para o estado de pedido visível ao cliente.
Para longos backlogs, considere se a carga de trabalho deveria ser um stream ou um sistema de armazenamento diferente. RabbitMQ pode manter filas, mas uma fila com milhões de mensagens antigas geralmente é um sinal de que os consumidores estão subdimensionados, os sistemas downstream estão falhando ou o processo de negócios precisa de semântica de replay em vez de semântica de fila.
Coloque limites de latência ao redor do cluster
O clustering do RabbitMQ espera links de rede confiáveis e de baixa latência. Esticar um cluster entre regiões distantes geralmente é uma má troca. O tráfego entre nós se torna mais lento, o failover se torna mais difícil de prever e uma partição de rede pode ser mais prejudicial do que a interrupção que você estava tentando evitar.
Um design prático é um cluster RabbitMQ por região, com roteamento em nível de aplicação ou federação/shovel entre regiões quando você precisa de movimento entre regiões. Isso mantém a publicação e o consumo locais rápidos. Também torna os domínios de falha claros: se a região A está não saudável, a região B não é arrastada para o mesmo problema de associação ao cluster.
Multi-AZ dentro de uma região é diferente. Se a latência entre as zonas é baixa e estável, três nós em três zonas podem funcionar bem. Teste sob carga real. O fato de um provedor de nuvem chamar algo de zona de disponibilidade não lhe diz como seus tamanhos de mensagem, confirmações e filas de quorum se comportarão durante uma hora movimentada.
Balanceie líderes de fila, não apenas nós
Um cluster pode parecer balanceado no gráfico de CPU e ainda estar severamente distorcido no nível da fila. Um nó pode possuir os líderes das filas mais movimentadas enquanto os outros seguram principalmente réplicas silenciosas.
Verifique o posicionamento das filas:
rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged
Se um nó possui a maioria dos líderes quentes, mova ou rebalanceie as filas usando as ferramentas suportadas do RabbitMQ para sua versão e tipo de fila. Para filas de quorum, o posicionamento dos membros e a localização do líder importam. Para filas clássicas, o posicionamento do mestre da fila importa em terminologia mais antiga, embora versões mais novas usem a linguagem de líder de forma mais consistente.
Uma boa topologia distribui filas quentes não relacionadas entre nós. Por exemplo, email.send, image.resize e billing.capture não devem ser todas lideradas pelo mesmo nó se cada uma tem tráfego pesado. Se billing.capture é a única fila quente, divida por um shard de negócios real, como grupo de comerciante ou região, apenas se os consumidores puderem processar esses shards de forma independente com segurança.
Projete o comportamento de conexão do cliente
O posicionamento da conexão do cliente faz parte da topologia. Se toda aplicação se conecta ao primeiro resultado de DNS para sempre, um nó pode carregar a maior parte do tráfego de clientes mesmo quando as filas estão distribuídas pelo cluster. Um balanceador de carga pode ajudar, mas deve usar verificações de saúde que entendam se um nó está realmente disponível para tráfego AMQP.
Mantenha as conexões de longa duração. RabbitMQ pode lidar com muitas conexões, mas a rotatividade de conexões consome CPU, memória, descritores de arquivo e overhead de TLS. Uma requisição web não deve abrir uma nova conexão AMQP, publicar uma mensagem e fechá-la. Use um pool de conexões ou canais apropriado para a biblioteca do cliente.
Também decida o que os clientes fazem durante uma falha de nó. Clientes bons reconectam com backoff, reabrem canais, redeclaram topologia privada se necessário e retomam confirmações ou consumo cuidadosamente. Clientes ruins reconectam em loops apertados e transformam uma reinicialização de nó em uma tempestade de conexões.
Para consumidores, pense em localidade, mas evite superajuste. Conectar um consumidor ao mesmo nó que o líder de sua fila pode reduzir o tráfego entre nós, mas os líderes de fila podem se mover após falhas. O consumidor deve sobreviver a isso sem alterações manuais.
Partições são eventos operacionais, não apenas configurações
Partições de rede são onde os diagramas de cluster são testados. RabbitMQ tem modos de tratamento de partição, mas nenhuma configuração remove a necessidade de uma decisão operacional clara. Se dois lados de um cluster não podem se comunicar, você deve decidir se a disponibilidade ou a consistência é mais importante para essa carga de trabalho.
Filas de quorum exigem uma maioria de réplicas. Esse é o ponto: um lado minoritário não deve continuar aceitando escritas que não podem ser acordadas com segurança. Isso pode surpreender equipes que esperavam que todo nó sobrevivente permanecesse gravável. Planeje para isso. Coloque membros de fila de quorum onde uma maioria possa sobreviver às falhas que você se importa.
Não espalhe uma fila de quorum de três nós por três regiões distantes e espere um comportamento suave durante a latência normal da internet. O quorum só será tão agradável quanto a rede entre os membros. Baixa latência e baixa perda de pacotes são requisitos de capacidade, não algo bom de se ter.
Execute simulações de partição em um ambiente não produtivo. Bloqueie o tráfego entre nós, observe quais filas permanecem disponíveis, veja os clientes reconectarem e anote as etapas de recuperação. A primeira vez que você aprender o comportamento da sua partição não deve ser durante um incidente de rede real.
Escale consumidores antes de escalar brokers
Quando o sintoma é uma fila crescendo, o broker nem sempre é o gargalo. Frequentemente os consumidores são simplesmente mais lentos que os publicadores. Antes de adicionar nós RabbitMQ, verifique a utilização do consumidor, contagens de não confirmados, tempo de processamento e latência downstream.
Se as mensagens estão prontas mas não confirmadas, RabbitMQ tem mensagens esperando e os consumidores não estão as pegando rápido o suficiente. Adicione consumidores, corrija o prefetch ou remova atrasos downstream. Se as mensagens estão principalmente não confirmadas, os consumidores já receberam trabalho e estão demorando muito para confirmá-lo. Adicionar nós broker não tornará esses manipuladores mais rápidos.
Prefetch importa aqui. Um prefetch de 500 em um trabalhador lento pode esconder um backlog dentro dos processos do consumidor. Um prefetch de 1 em um trabalhador local rápido pode desperdiçar tempo em viagens de ida e volta. Comece com um valor pequeno, meça a latência de ponta a ponta e a memória do consumidor, depois ajuste.
Observe os limites chatos
Planos de escalonamento frequentemente falam sobre topologia e esquecem descritores de arquivo, alarmes de disco, alarmes de memória, rotatividade de conexões e contagens de canais. RabbitMQ é sensível a todos eles.
Para cada nó, monitore memória usada, disco livre, descritores de arquivo, sockets, memória do processo da fila, taxas de mensagem, latência de confirmação e utilização do escalonador Erlang. No lado do cliente, monitore loops de reconexão e taxas de criação de canais. Um serviço que abre uma nova conexão para cada publicação pode prejudicar um cluster muito antes do volume de mensagens parecer impressionante.
Use conexões e canais de longa duração onde sua biblioteca cliente os suporta. Coloque limites de conexão e configurações de heartbeat no design, não em uma mudança de pânico durante uma interrupção.
Uma topologia que geralmente funciona
Para uma aplicação de negócios típica, eu começaria com três nós RabbitMQ em uma região, distribuídos entre zonas se a rede for boa. Use filas de quorum para fluxos de trabalho duráveis importantes. Use filas clássicas para trabalho transitório onde o comportamento de falha é aceitável. Mantenha publicadores e consumidores próximos ao cluster. Use um balanceador de carga para acesso do cliente, mas verifique o equilíbrio do líder da fila em vez de assumir que o balanceador de carga resolveu o posicionamento do broker.
Depois teste os casos feios: mate um nó, pause um grupo de consumidores, encha uma fila, lentifique o disco e reinicie um publicador. Escalar RabbitMQ é menos sobre o diagrama mais bonito e mais sobre saber o que acontece quando o diagrama é estressado.