Risoluzione dei problemi di elaborazione lenta dei messaggi: identificazione dei colli di bottiglia di RabbitMQ

Diagnostica i rallentamenti di RabbitMQ separando i colli di bottiglia di produttore, broker, coda, consumatore, disco e conferma.

Risoluzione dei problemi di elaborazione lenta dei messaggi: identificazione dei colli di bottiglia di RabbitMQ

Quando una coda di RabbitMQ si accumula, la coda mostra solo il sintomo. Il collo di bottiglia potrebbe essere un consumatore lento, un publisher bloccato, un allarme del disco, un valore di prefetch errato, un payload di messaggio enorme o un database a valle che ha iniziato silenziosamente a timeout. Riavviare RabbitMQ può ripulire il grafico per qualche minuto, ma raramente risolve il motivo per cui i messaggi erano lenti.

Il percorso di risoluzione dei problemi più rapido è separare il flusso in parti: pubblicazione in RabbitMQ, routing alle code, archiviazione dei messaggi, consegna ai consumatori, elaborazione del lavoro e riconoscimento del completamento. Ogni pezzo lascia prove diverse.

Prima suddivisione: pronto o non riconosciuto

Inizia con i contatori della coda:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers state

messages_ready significa che i messaggi sono in coda in attesa di essere consegnati. Se questo numero cresce, RabbitMQ non ha consumatori disponibili, i consumatori sono al limite del prefetch o la consegna è bloccata da un'altra condizione.

messages_unacknowledged significa che i messaggi sono già stati consegnati ai consumatori e RabbitMQ attende un ack, nack, rifiuto o chiusura del canale. Se questo numero cresce, il collo di bottiglia è solitamente all'interno del consumatore o di qualcosa che il consumatore chiama.

Questa distinzione è importante. Se i messaggi pronti sono molti e quelli non riconosciuti sono pochi, aggiungere più memoria del broker non farà apparire i consumatori. Se i non riconosciuti sono molti, aggiungere più partizioni della coda potrebbe non aiutare perché il lavoro è già uscito dalla coda.

Verifica se i consumatori sono effettivamente presenti

Un numero sorprendente di incidenti "RabbitMQ è lento" sono in realtà incidenti "i consumatori non sono in esecuzione". Distribuzione fallita, autoscaling azzerato, credenziali modificate, virtual host sbagliato utilizzato o il servizio è connesso allo staging mentre i produttori pubblicano in produzione.

Usa:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

Se non ci sono consumatori, risolvi prima quello. Se i consumatori sono presenti ma inattivi, controlla i log dell'applicazione e lo stato della connessione. Se ogni consumatore ha un prefetch di 1 e ogni messaggio richiede diversi secondi, una bassa concorrenza di consegna potrebbe essere prevista. Se ogni consumatore ha un prefetch di 500 e i non riconosciuti sono enormi, i consumatori potrebbero accumulare lavoro che non riescono a completare rapidamente.

Misura il tempo di elaborazione del consumatore

RabbitMQ può dirti che i messaggi non sono riconosciuti. Non può dirti se il consumatore sta analizzando un payload enorme, aspettando PostgreSQL, riprovando una chiamata HTTP o bloccato su un lock.

Aggiungi temporizzazione attorno al gestore reale:

message_received_at
decode_ms
business_logic_ms
database_ms
external_api_ms
ack_ms
message_completed_at

Non hai bisogno di un sistema di tracciamento perfetto per imparare qualcosa. Anche pochi campi di log strutturati possono mostrare che il gestore normalmente impiega 80 ms ma ora spende 4 secondi aspettando un'API a valle.

Se il lavoro è lento ma parallelizzabile, aggiungi istanze del consumatore o aumenta la concorrenza interna dei worker. Se il sistema a valle è il limite, aggiungere consumatori potrebbe peggiorare le cose. Potresti aver bisogno di limitazione della velocità, batch, caching o una coda di retry separata.

Ottimizza il prefetch dopo aver compreso il gestore

Il prefetch controlla quanti messaggi non riconosciuti RabbitMQ può inviare a ciascun consumatore. È spesso coinvolto in incidenti di elaborazione lenta perché cambia dove è visibile l'arretrato.

Con prefetch basso, i messaggi rimangono pronti in RabbitMQ finché un consumatore non è pronto per altri. Questo è equo e facile da osservare, ma può sottoutilizzare consumatori molto veloci.

Con prefetch alto, i messaggi si spostano rapidamente nei consumatori. Questo può migliorare il throughput per gestori veloci, ma può anche nascondere la latenza. Un consumatore lento con un valore di prefetch grande può accumulare centinaia di messaggi mentre altri consumatori esauriscono il lavoro.

Una mossa pratica in caso di incidente è abbassare il prefetch per consumatori lenti o instabili e osservare se la latenza di coda migliora. Per consumatori veloci con basso utilizzo della CPU e conteggi di pronti elevati, aumenta cautamente il prefetch e misura di nuovo.

Cerca colli di bottiglia dal lato del publisher

A volte la coda non si accumula perché i consumatori sono lenti. Si accumula perché i produttori pubblicano a raffiche e poi aspettano in modo inefficiente le conferme.

Le conferme del publisher sono lo strumento giusto quando i publisher devono sapere che RabbitMQ ha accettato i messaggi. Il modello lento è aspettare ogni conferma prima di pubblicare il messaggio successivo. Questo trasforma ogni pubblicazione in un round trip.

Modelli migliori usano conferme asincrone o batch limitati. Il publisher può mantenere un numero limitato di messaggi in volo, gestire i nack ed evitare comunque di bloccarsi su ogni singolo messaggio. Il limite è importante. La pubblicazione illimitata in volo può spostare il collo di bottiglia nella memoria del publisher o nella pressione del broker.

Controlla le metriche del publisher: tasso di pubblicazione, latenza di conferma, conteggio delle conferme in volo, riconnessioni, messaggi restituiti ed eccezioni del canale. Nell'interfaccia di gestione, confronta i tassi di pubblicazione in entrata con i tassi di consegna/ack. Se la pubblicazione in entrata è bassa anche se l'applicazione è occupata, il produttore potrebbe aspettare conferme, transazioni o cambi di connessione.

Evita le transazioni AMQP per la pubblicazione ad alto throughput a meno che non ci sia un motivo specifico. Sono molto più costose delle conferme del publisher per la pubblicazione affidabile tipica.

Controlla il disco prima di incolpare RabbitMQ

I messaggi persistenti, le code di quorum, gli stream e i grandi arretrati coinvolgono tutti il disco. Quando la latenza del disco aumenta, il flusso dei messaggi può rallentare drasticamente.

Sul nodo RabbitMQ, controlla:

rabbitmq-diagnostics status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics memory_breakdown

A livello di sistema operativo, usa strumenti come iostat, vmstat o i tuoi grafici di monitoraggio cloud. Guarda la latenza del disco e l'attesa I/O, non solo il throughput. Un disco cloud che ha esaurito i crediti burst può sembrare normale nella configurazione e terribile nella pratica.

Se il disco è il collo di bottiglia, le possibili soluzioni includono storage più veloce, meno scritture persistenti, messaggi più piccoli, miglior batch delle conferme del publisher, suddivisione della coda o spostamento dei carichi di lavoro di tipo replay su stream o un altro sistema orientato ai log. Non disabilitare la persistenza per messaggi che l'azienda non può permettersi di perdere solo per rendere verde un grafico.

Controlla allarmi e connessioni bloccate

RabbitMQ si protegge con allarmi di memoria e disco. Quando un allarme è attivo, i publisher possono essere bloccati. Questo può sembrare lentezza dell'applicazione dal lato del produttore.

Esegui:

rabbitmq-diagnostics alarms
rabbitmqctl list_connections name user state channels send_pend recv_cnt send_cnt

Se gli allarmi di memoria sono attivi, scopri se la memoria è occupata da code, connessioni, messaggi non riconosciuti, binari o plugin. Se gli allarmi del disco sono attivi, libera spazio o aggiungi capacità prima di provare a inviare più messaggi attraverso il broker.

Le connessioni bloccate non sono un bug di per sé. Sono RabbitMQ che dice ai publisher di rallentare perché il nodo sta proteggendo la disponibilità.

La dimensione del messaggio può essere il colpevole silenzioso

Un sistema che gestisce 10.000 messaggi minuscoli al secondo può avere difficoltà con 500 messaggi grandi al secondo. I payload grandi aumentano il trasferimento di rete, la pressione della memoria, le scritture su disco, il lavoro di garbage collection e il tempo di elaborazione del consumatore.

Se i messaggi contengono documenti grandi, immagini, report o array grandi, considera di archiviare il payload in object storage o un database e inviare un riferimento tramite RabbitMQ. Includi metadati sufficienti per il routing e l'idempotenza, ma tieni il broker fuori dal ruolo di storage bulk quando possibile.

Controlla anche le scelte di compressione. Comprimere payload enormi può ridurre l'uso della rete e del disco ma aumentare la CPU. Se questo aiuta dipende da dove si trova il collo di bottiglia.

I retry possono creare il collo di bottiglia

Un servizio a valle che fallisce può trasformare un messaggio in molti tentativi. Se i consumatori riaccodano immediatamente i fallimenti, possono elaborare ripetutamente gli stessi messaggi errati mentre il lavoro fresco aspetta. La profondità della coda può aumentare, la CPU può sembrare occupata e pochissimo lavoro utile viene svolto.

Cerca alti tassi di riconsegna e log di errore ripetuti con gli stessi ID messaggio. Se lo stesso payload fallisce ripetutamente, spostalo fuori dal flusso principale. Un exchange di dead-letter, una coda di retry ritardata o un meccanismo di retry programmato dà alla dipendenza il tempo di riprendersi e impedisce ai messaggi velenosi di bloccare il lavoro normale.

Fai attenzione alle tempeste di retry. Se un'API è giù per dieci minuti e ogni messaggio riprova ogni secondo, il recupero diventa più difficile quando l'API torna. Usa backoff. Limita i tentativi. Rendi visibile il fallimento finale in una coda di dead-letter con abbastanza contesto per indagare.

L'idempotenza fa anche parte della risoluzione dei problemi di prestazioni. Se un consumatore riprova dopo aver completato parzialmente il lavoro, i duplicati possono creare contesa nel database, errori di chiave univoca o chiamate a valle extra. Un gestore che può elaborare in sicurezza lo stesso messaggio due volte è molto più facile da scalare e recuperare.

I tassi dell'interfaccia di gestione necessitano di contesto

L'interfaccia di gestione di RabbitMQ è utile, ma i grafici dei tassi possono trarre in inganno se leggi una sola linea. Un alto tasso di consegna con un basso tasso di ack significa che il lavoro viene distribuito più velocemente di quanto venga completato. Un alto tasso di ack con un alto conteggio di pronti può significare che i consumatori stanno lavorando ma non abbastanza per recuperare. Un basso tasso di pubblicazione durante un incidente può significare che i produttori sono bloccati o aspettano conferme.

Guarda diversi tassi insieme:

  • publish: messaggi che entrano negli exchange.
  • deliver/get: messaggi inviati ai consumatori.
  • ack: messaggi completati dai consumatori.
  • redeliver: messaggi consegnati di nuovo dopo un fallimento precedente o chiusura del canale.

Per una coda di lavoro sana e stabile, i tassi di pubblicazione e ack dovrebbero essere vicini nel tempo. Brevi raffiche sono normali. Lunghi gap significano che l'arretrato si sta accumulando o svuotando. Se le riconsegne aumentano bruscamente, non aggiungere solo più consumatori. Scopri perché i messaggi tornano indietro.

Le finestre di campionamento contano. Un grafico di un minuto può nascondere un blocco di cinque secondi che danneggia gli utenti. Un grafico di un secondo può far sembrare la normale burstiness come caos. Abbina la finestra del grafico alla latenza a cui tengono i tuoi utenti o sistemi a valle.

Separa l'arretrato normale dall'arretrato rotto

Non tutti gli arretrati sono un'emergenza. Un sistema batch può accodare intenzionalmente il lavoro durante il giorno e svuotarlo di notte. Un flusso di lavoro rivolto all'utente può essere malsano se i messaggi aspettano per trenta secondi. La stessa profondità di coda può essere accettabile in un sistema e grave in un altro.

Definisci un segnale basato sull'età, non solo un conteggio. Il conteggio dei messaggi ti dice quanti stanno aspettando; l'età del messaggio ti dice se l'azienda sta rimanendo indietro. Se il tuo monitoraggio può tracciare l'età del messaggio più vecchio o il tempo end-to-end dalla pubblicazione all'ack, catturerà i rallentamenti prima della sola profondità della coda.

Collega gli avvisi a quella aspettativa. Avvisare su 10.000 messaggi può essere rumoroso per una coda di esportazione notturna e troppo tardi per una coda di reimpostazione della password. Avvisare su "messaggio più vecchio più vecchio dell'obiettivo di servizio" è solitamente più vicino a ciò che interessa agli utenti.

Una coda calda è ancora una coda calda

Aggiungere nodi cluster non suddivide automaticamente una coda su tutti i nodi. Una singola coda calda può rimanere limitata dal suo leader, dai suoi consumatori e dal suo percorso di archiviazione.

Se una coda trasporta tipi di lavoro non correlati, suddividila per comportamento di elaborazione reale. Ad esempio, il ridimensionamento delle immagini, l'invio di email e la cattura della fatturazione non dovrebbero condividere una coda generica jobs se hanno esigenze di latenza e retry diverse. Code separate ti permettono di scalare i consumatori in modo indipendente e isolare i messaggi velenosi.

Se un tipo di lavoro è ancora troppo caldo, partiziona solo quando i requisiti di ordinamento lo consentono. Il partizionamento per ID cliente, tenant, regione o un'altra chiave stabile può funzionare, ma spinge la complessità nel routing e nelle operazioni. Non partizionare solo per evitare di riparare un gestore lento.

Un ordine di risoluzione dei problemi calmo

In un incidente, uso questo ordine:

  1. Controlla gli allarmi: memoria, disco e connessioni bloccate.
  2. Controlla i contatori della coda: pronti, non riconosciuti, consumatori.
  3. Controlla i log del consumatore e la temporizzazione del gestore.
  4. Controlla il prefetch e la distribuzione dei non riconosciuti per consumatore.
  5. Controlla la latenza di conferma del publisher e i messaggi restituiti.
  6. Controlla la latenza del disco e la pressione delle risorse del nodo.
  7. Controlla la dimensione del messaggio e le recenti modifiche al payload.
  8. Solo allora modifica la topologia o aggiungi nodi broker.

Questo ordine previene un errore comune: scalare il broker quando il collo di bottiglia è un worker, o scalare i worker quando il collo di bottiglia è il disco.

RabbitMQ è solitamente molto chiaro una volta che leggi i contatori giusti. Un conteggio di pronti in crescita dice che il lavoro sta aspettando. Un conteggio di non riconosciuti in crescita dice che il lavoro è in corso ma non sta finendo. Un publisher bloccato dice che il broker si sta proteggendo. Tratta ogni segnale come un indizio e la soluzione diventa molto meno drammatica.