Scaling RabbitMQ: Guida all'Ottimizzazione delle Topologie di Cluster

Progetta cluster RabbitMQ che scalano senza confondere clustering, replica e throughput.

Scaling RabbitMQ: Guida all'Ottimizzazione delle Topologie di Cluster

Scalare RabbitMQ inizia con un fatto scomodo: un cluster non è un broker magico più grande. È un insieme di broker che condividono metadati e, a seconda del tipo di coda, possono replicare i dati della coda. Se una singola coda è sovraccarica, aggiungere nodi attorno può migliorare la disponibilità, ma non renderà automaticamente quella coda più veloce da consumare.

Questa distinzione evita molti progetti sbagliati. Ho visto team aggiungere due nodi a un deployment RabbitMQ occupato, non spostare nulla, non modificare la disposizione delle code, e chiedersi perché la stessa coda si accumulasse ancora ogni pomeriggio. Il leader della coda era ancora sullo stesso nodo. Gli stessi consumatori stavano ancora facendo lo stesso lavoro. Il cluster aveva più macchine, ma il collo di bottiglia non si era spostato.

La topologia del cluster RabbitMQ riguarda principalmente decidere dove vivono le code, quante copie dei messaggi importanti servono, e quanto guasto si può tollerare prima che il throughput cali. La risposta giusta per una pipeline di metriche a breve termine non è la stessa per pagamenti, evasione ordini o eventi di audit.

Cosa condivide effettivamente il clustering

I nodi RabbitMQ in un cluster condividono definizioni: virtual host, utenti, permessi, exchange, code, binding, policy e metadati di runtime necessari per il funzionamento del cluster. Un produttore connesso a un nodo può pubblicare su un exchange il cui leader della coda è su un altro nodo. Un consumatore può connettersi a un nodo diverso da quello che ospita la coda.

Questo non significa che ogni messaggio esista ovunque.

Le code classiche hanno un leader su un nodo. Le code di quorum hanno un leader più repliche. Gli stream hanno il proprio modello di replica. Se un leader di coda è remoto rispetto alla maggior parte dei client che lo utilizzano, RabbitMQ deve spostare il traffico attraverso l'interconnessione del cluster. Va bene con moderazione. Diventa costoso quando ogni publisher si connette al nodo A, ogni coda calda vive sul nodo B, e ogni consumatore si connette al nodo C.

Una prima regola semplice funziona bene: connetti le applicazioni ai nodi vicini alle code che usano, o metti un bilanciatore di carico davanti al cluster e verifica che i leader delle code siano ragionevolmente bilanciati. Non dare per scontato che connessioni client round-robin creino carico di coda round-robin.

Preferisci tre nodi prima di diventare creativo

Per la maggior parte dei cluster RabbitMQ di produzione, tre nodi in un gruppo a bassa latenza o zona di disponibilità è il punto di partenza pulito. Dà alle code di quorum un modello di maggioranza che può sopravvivere al guasto di un nodo, e mantiene il coordinamento del cluster abbastanza semplice da ragionare durante un incidente.

I cluster a due nodi sembrano più economici, ma sono scomodi per le code replicate. Con i sistemi basati su quorum, è necessaria una maggioranza. Se uno dei due nodi scompare, non c'è maggioranza. Puoi aggiungere un terzo nodo di tipo witness in alcuni sistemi distribuiti, ma per RabbitMQ è solitamente più semplice e affidabile eseguire tre nodi reali con sufficiente capacità di disco e rete.

Cinque nodi possono avere senso quando hai molte code, hai bisogno di più opzioni di posizionamento, o vuoi distribuire il carico su più macchine. Aumenta anche la quantità di comunicazione del cluster e la superficie operativa. Prima di passare da tre a cinque, verifica se stai risolvendo la saturazione del nodo o un problema di progettazione delle code. Se una coda è calda, più nodi da soli non divideranno il lavoro di quella coda.

Le code di quorum sono per affidabilità replicata, non per velocità gratuita

Per nuovi carichi di lavoro ad alta disponibilità, le code di quorum sono solitamente l'impostazione predefinita corretta quando la durabilità dei messaggi è importante. Replicano i messaggi usando un protocollo di consenso. Una coda di quorum con tre membri può continuare a funzionare se un membro non è disponibile, purché una maggioranza rimanga sana.

Il compromesso è il costo di scrittura. Un messaggio persistente pubblicato deve essere replicato a un numero sufficiente di membri prima di essere considerato accettato in sicurezza. Questo è esattamente ciò che vuoi per lavori importanti, ma non è lo stesso profilo di prestazioni di una coda classica transitoria.

Dichiara una coda di quorum con un argomento o una policy, a seconda di come la tua applicazione gestisce la topologia:

rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'

Per le policy, delimitatele attentamente. Non convertire accidentalmente ogni coda in un virtual host in una coda di quorum solo perché un pattern ampio ha corrisposto .*. Un buon nome di policy e un prefisso di coda ristretto sono noiosi nel modo migliore:

rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues

Se stai migrando da code classiche con mirroring, trattalo come una migrazione, non come un cambio di bandiera. Le code classiche con mirroring e le code di quorum si comportano diversamente per quanto riguarda ordinamento, messaggi velenosi, uso della memoria e failover. Crea il nuovo tipo di coda, instrada una porzione controllata di traffico, osserva i confirm e la latenza dei consumatori, poi sposta il resto.

Le code classiche hanno ancora un posto

Le code classiche sono ancora utili per carichi di lavoro dove la replica non è richiesta, dove i messaggi sono transitori, o dove la coda è locale a un servizio e può essere ricostruita da un'altra fonte. Sono anche una scelta ragionevole per eventi ad alto volume e basso valore dove perdere alcuni messaggi durante un guasto di nodo è accettabile.

Usa le code classiche deliberatamente. Se una coda classica è durevole e riceve messaggi persistenti, quei messaggi sono memorizzati sul nodo che ospita quella coda. Se quel nodo è giù, la coda non è disponibile fino al ritorno del nodo. Questo può andare bene per un lavoro di riconciliazione in background. Di solito non va bene per lo stato degli ordini visibile al cliente.

Per lunghi arretrati, considera se il carico di lavoro dovrebbe essere uno stream o un sistema di archiviazione diverso. RabbitMQ può gestire code, ma una coda con milioni di vecchi messaggi è spesso un segnale che i consumatori sono sottodimensionati, i sistemi a valle stanno fallendo, o il processo aziendale ha bisogno di semantica di replay piuttosto che di semantica di coda.

Metti confini di latenza attorno al cluster

Il clustering di RabbitMQ si aspetta collegamenti di rete a bassa latenza e affidabili. Allungare un singolo cluster attraverso regioni distanti è di solito un cattivo compromesso. Il traffico tra nodi diventa più lento, il failover diventa più difficile da prevedere, e una partizione di rete può essere più dannosa del guasto che stavi cercando di evitare.

Un progetto pratico è un cluster RabbitMQ per regione, con routing a livello di applicazione o federation/shovel tra regioni quando hai bisogno di movimento tra regioni. Questo mantiene la pubblicazione e il consumo locali veloci. Rende anche chiari i domini di guasto: se la regione A è malsana, la regione B non viene trascinata nello stesso problema di appartenenza al cluster.

Multi-AZ all'interno di una regione è diverso. Se la latenza tra le zone è bassa e stabile, tre nodi attraverso tre zone possono funzionare bene. Testalo sotto carico reale. Il fatto che un provider cloud chiami qualcosa zona di disponibilità non ti dice come si comporteranno le dimensioni dei tuoi messaggi, i confirm e le code di quorum durante un'ora di punta.

Bilancia i leader delle code, non solo i nodi

Un cluster può sembrare bilanciato nel grafico della CPU ed essere ancora fortemente sbilanciato a livello di coda. Un nodo può possedere i leader per le code più trafficate mentre gli altri detengono principalmente repliche tranquille.

Controlla il posizionamento delle code:

rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged

Se un nodo possiede la maggior parte dei leader caldi, sposta o ribilancia le code usando gli strumenti supportati di RabbitMQ per la tua versione e tipo di coda. Per le code di quorum, il posizionamento dei membri e la posizione del leader sono importanti. Per le code classiche, il posizionamento del master della coda è importante nella terminologia più vecchia, sebbene le versioni più recenti usino il linguaggio del leader in modo più coerente.

Una buona topologia distribuisce code calde non correlate tra i nodi. Ad esempio, email.send, image.resize e billing.capture non dovrebbero essere tutti guidati dallo stesso nodo se ciascuno ha traffico pesante. Se billing.capture è l'unica coda calda, dividi per uno shard aziendale reale come gruppo di commercianti o regione solo se i consumatori possono elaborare in sicurezza quegli shard in modo indipendente.

Progetta il comportamento di connessione del client

Il posizionamento della connessione del client fa parte della topologia. Se ogni applicazione si connette al primo risultato DNS per sempre, un nodo può portare la maggior parte del traffico client anche quando le code sono distribuite attraverso il cluster. Un bilanciatore di carico può aiutare, ma dovrebbe usare controlli di salute che capiscano se un nodo è effettivamente disponibile per il traffico AMQP.

Mantieni le connessioni di lunga durata. RabbitMQ può gestire molte connessioni, ma il churn di connessione consuma CPU, memoria, descrittori di file e overhead TLS. Una richiesta web non dovrebbe aprire una nuova connessione AMQP, pubblicare un messaggio e chiuderla. Usa un pool di connessioni o canali appropriato per la libreria client.

Decidi anche cosa fanno i client durante il guasto del nodo. I buoni client si riconnettono con backoff, riaprono i canali, ridefiniscono la topologia privata se necessario, e riprendono i confirm o il consumo con attenzione. I cattivi client si riconnettono in cicli stretti e trasformano un riavvio del nodo in una tempesta di connessioni.

Per i consumatori, pensa alla località ma evita l'overfitting. Connettere un consumatore allo stesso nodo del suo leader di coda può ridurre il traffico tra nodi, ma i leader delle code possono spostarsi dopo i guasti. Il consumatore dovrebbe sopravvivere a questo senza modifiche manuali.

Le partizioni sono eventi operativi, non solo impostazioni

Le partizioni di rete sono dove i diagrammi del cluster vengono testati. RabbitMQ ha modalità di gestione delle partizioni, ma nessuna impostazione rimuove la necessità di una chiara decisione operativa. Se due lati di un cluster non possono parlare, devi decidere se la disponibilità o la coerenza è più importante per quel carico di lavoro.

Le code di quorum richiedono una maggioranza di repliche. Questo è il punto: un lato di minoranza non dovrebbe continuare ad accettare scritture che non possono essere concordate in sicurezza. Questo può sorprendere i team che si aspettavano che ogni nodo sopravvissuto rimanesse scrivibile. Pianifica per questo. Metti i membri della coda di quorum dove una maggioranza può sopravvivere ai guasti che ti interessano.

Non distribuire una coda di quorum a tre nodi attraverso tre regioni distanti e aspettarti un comportamento fluido durante la normale latenza di internet. Il quorum sarà piacevole solo quanto la rete tra i membri. Bassa latenza e bassa perdita di pacchetti sono requisiti di capacità, non optional.

Esegui esercitazioni sulle partizioni in un ambiente non di produzione. Blocca il traffico tra i nodi, osserva quali code rimangono disponibili, guarda i client riconnettersi, e scrivi i passaggi di ripristino. La prima volta che impari il comportamento delle tue partizioni non dovrebbe essere durante un vero incidente di rete.

Scala i consumatori prima di scalare i broker

Quando il sintomo è una coda in crescita, il broker non è sempre il collo di bottiglia. Spesso i consumatori sono semplicemente più lenti dei publisher. Prima di aggiungere nodi RabbitMQ, controlla l'utilizzo dei consumatori, i conteggi non riconosciuti, il tempo di elaborazione e la latenza a valle.

Se i messaggi sono pronti ma non non riconosciuti, RabbitMQ ha messaggi in attesa e i consumatori non li stanno prendendo abbastanza velocemente. Aggiungi consumatori, correggi il prefetch o rimuovi i ritardi a valle. Se i messaggi sono per lo più non riconosciuti, i consumatori hanno già ricevuto lavoro e stanno impiegando troppo tempo per riconoscerlo. Aggiungere nodi broker non renderà quei gestori più veloci.

Il prefetch è importante qui. Un prefetch di 500 su un lavoratore lento può nascondere un arretrato all'interno dei processi consumatori. Un prefetch di 1 su un lavoratore locale veloce può sprecare tempo in round trip. Inizia con un valore piccolo, misura la latenza end-to-end e la memoria del consumatore, poi regola.

Controlla i limiti noiosi

I piani di scaling spesso parlano di topologia e dimenticano descrittori di file, allarmi di disco, allarmi di memoria, churn di connessione e conteggi di canali. RabbitMQ è sensibile a tutti questi.

Per ogni nodo, monitora la memoria utilizzata, il disco libero, i descrittori di file, i socket, la memoria del processo di coda, i tassi di messaggio, la latenza di conferma e l'utilizzo dello scheduler Erlang. Sul lato client, monitora i cicli di riconnessione e i tassi di creazione dei canali. Un servizio che apre una nuova connessione per ogni pubblicazione può danneggiare un cluster molto prima che il volume di messaggi sembri impressionante.

Usa connessioni e canali di lunga durata dove la tua libreria client li supporta. Metti i limiti di connessione e le impostazioni di heartbeat nel progetto, non in una modifica di panico durante un'interruzione.

Una topologia che di solito funziona

Per un'applicazione aziendale tipica, inizierei con tre nodi RabbitMQ in una regione, distribuiti tra zone se la rete è buona. Usa code di quorum per flussi di lavoro durevoli importanti. Usa code classiche per lavoro transitorio dove il comportamento di guasto è accettabile. Mantieni publisher e consumatori vicini al cluster. Usa un bilanciatore di carico per l'accesso client, ma verifica l'equilibrio dei leader delle code piuttosto che presumere che il bilanciatore di carico abbia risolto il posizionamento del broker.

Poi testa i casi brutti: uccidi un nodo, metti in pausa un gruppo di consumatori, riempi una coda, rallenta il disco e riavvia un publisher. Scalare RabbitMQ riguarda meno il diagramma più bello e più il sapere cosa succede quando il diagramma è sotto stress.