RabbitMQ Prefetch-Einstellungen für optimale Consumer-Leistung meistern
Passen Sie RabbitMQ Prefetch an, damit Consumer ausgelastet bleiben, ohne Nachrichten zu horten oder langsame Verarbeitung zu verstecken.
RabbitMQ Prefetch-Einstellungen für optimale Consumer-Leistung meistern
RabbitMQ Prefetch ist eine dieser Einstellungen, die winzig aussieht und alles verändert. Sie steuert, wie viele nicht bestätigte Nachrichten RabbitMQ einem Consumer gleichzeitig erlauben darf. Zu niedrig eingestellt, verbringen schnelle Consumer zu viel Zeit mit Warten auf die nächste Zustellung. Zu hoch eingestellt, horten langsame Consumer leise Arbeit, erhöhen die Latenz und lassen Queue-Tiefendiagramme lügen.
Die nützliche Art, über Prefetch nachzudenken, ist unerledigte Arbeit. Ein Prefetch von 20 bedeutet, dass ein Consumer 20 Nachrichten zugestellt, aber noch nicht bestätigt haben kann. Diese Nachrichten sind nicht mehr ready in der Queue. Sie sind unacked, liegen beim Consumer, bis er sie bestätigt (ack), ablehnt (nack), zurückweist (reject) oder die Verbindung trennt.
Das bedeutet, Prefetch ist nicht nur ein Durchsatzregler. Es ist ein Fairness-Regler, ein Speicherregler und ein Fehlerbehandlungsregler.
Was basic.qos in RabbitMQ bewirkt
Consumer setzen Prefetch mit basic.qos. In den meisten Client-Bibliotheken setzen Sie prefetch_count; prefetch_size wird selten verwendet und bleibt normalerweise bei Null.
In Python mit Pika:
channel.basic_qos(prefetch_count=10)
channel.basic_consume(
queue="jobs",
on_message_callback=handle_message,
auto_ack=False,
)
In Node.js mit amqplib:
await channel.prefetch(10);
await channel.consume("jobs", async (msg) => {
try {
await handleMessage(msg.content);
channel.ack(msg);
} catch (err) {
channel.nack(msg, false, false);
}
}, { noAck: false });
Manuelle Bestätigung ist wichtig. Wenn Sie automatische Bestätigungen verwenden, betrachtet RabbitMQ die Nachricht als abgeschlossen, sobald sie zugestellt wurde. Prefetch schützt die Verarbeitungszuverlässigkeit dann nicht mehr auf die gleiche Weise, da es kein Fenster nicht bestätigter Nachrichten gibt, das verwaltet werden muss.
RabbitMQ wendet Prefetch standardmäßig pro Consumer an, auch wenn die ursprüngliche Formulierung von AMQP kanalorientiert ist. Einige Clients legen ein global-Flag offen. Seien Sie vorsichtig damit. Eine gemeinsame Kanal- oder verbindungsweite Begrenzung kann verwirrende Wechselwirkungen zwischen Consumern erzeugen. Die meisten Dienste sind einfacher zu verstehen, wenn jeder Consumer seinen eigenen Kanal und seine eigene Prefetch-Anzahl hat.
Warum Prefetch die Latenz verändert
Stellen Sie sich eine Queue mit zwei Consumern vor. Consumer A erhält einen Batch von 100 Nachrichten und trifft dann auf eine langsame externe API. Consumer B ist gesund und schnell, aber diese 100 Nachrichten sind bereits A zugewiesen. RabbitMQ wird sie B nicht geben, es sei denn, A lehnt sie ab oder sein Kanal schließt sich.
Aus Sicht der Queue sind diese Nachrichten nicht bereit. Aus Sicht des Benutzers sind sie verzögert. Deshalb kann ein hoher Prefetch ein System in Broker-Diagrammen besser aussehen lassen, während die tatsächliche Latenz schlechter wird.
Niedriger Prefetch gibt RabbitMQ mehr Möglichkeiten, Arbeit fair zu verteilen. Hoher Prefetch gibt Consumern mehr lokale Arbeit und weniger Broker-Roundtrips. Keines ist immer richtig.
Startwerte, die sinnvoll sind
Für langsame Jobs fangen Sie klein an. Wenn jede Nachricht eine Drittanbieter-API aufruft, mehrere Datenbankzeilen schreibt oder CPU-intensive Transformationen durchführt, versuchen Sie prefetch_count=1 bis 10. Sie möchten, dass ein fehlgeschlagener oder langsamer Consumer nur eine kleine Menge Arbeit hält.
Für mittlere Jobs, die Dutzende oder Hunderte von Millisekunden dauern und auf stabilen Workern laufen, sind Werte wie 10, 20 oder 50 übliche Ausgangspunkte. Messen Sie, bevor Sie höher gehen.
Für sehr schnelle Handler, bei denen Broker und Consumer in einem Netzwerk mit niedriger Latenz sind, kann ein höherer Prefetch Roundtrips reduzieren und den Durchsatz verbessern. Auch dann vermeiden Sie es, eine riesige Zahl zu wählen, nur weil sie einen Benchmark für fünf Minuten gut aussehen ließ. Beobachten Sie den Consumer-Speicher und die Tail-Latenz.
Eine einfache Faustregel ist, Prefetch um die Menge an Arbeit zu dimensionieren, die ein Consumer bequem für ein kurzes Zeitfenster halten kann. Wenn ein Worker etwa 20 Nachrichten pro Sekunde verarbeitet und Sie mit etwa einer Sekunde lokaler gepufferter Arbeit zufrieden sind, ist ein Prefetch nahe 20 ein vernünftiges Experiment.
Wie man erkennt, ob Prefetch zu hoch ist
Prefetch ist wahrscheinlich zu hoch, wenn:
messages_unacknowledgedim Vergleich zu aktiven Consumern groß ist.- Einige Consumer viele nicht bestätigte Nachrichten haben, während andere im Leerlauf sind.
- Die Nachrichtenlatenz hoch ist, selbst wenn
messages_readyniedrig ist. - Der Consumer-Speicher während Bursts ansteigt.
- Ein Consumer-Absturz eine große Welle von erneuten Zustellungen verursacht.
Der letzte Punkt ist leicht zu übersehen. Wenn ein Worker 1.000 nicht bestätigte Nachrichten hält und abstürzt, kann RabbitMQ diese Nachrichten erneut zustellen. Das ist korrektes Verhalten, kann aber doppelten Druck auf nachgelagerte Systeme erzeugen, wenn der Handler nicht idempotent ist.
Die Senkung des Prefetch verbessert oft die Fairness und das Wiederherstellungsverhalten. Es kann den Spitzendurchsatz etwas reduzieren, aber die Latenz, die Benutzer tatsächlich spüren, verbessern.
Wie man erkennt, ob Prefetch zu niedrig ist
Prefetch ist wahrscheinlich zu niedrig, wenn:
- Consumer eine niedrige CPU- und Speichernutzung haben, während
messages_readyweiter wächst. - Die Verarbeitungszeit sehr kurz ist, aber die Zustellrate begrenzt ist.
- Die Netzwerklatenz zwischen Consumern und RabbitMQ spürbar ist.
- Eine Erhöhung des Prefetch den Durchsatz verbessert, ohne die Tail-Latenz oder den Speicherdruck zu erhöhen.
Das klassische Beispiel ist ein schneller Worker, der eine kleine In-Memory-Berechnung durchführt und sofort bestätigt. Mit prefetch_count=1 kann er zu viel Zeit mit Warten auf die nächste Nachricht verbringen. Eine Erhöhung des Prefetch gibt ihm einen kleinen lokalen Puffer und hält ihn beschäftigt.
Verstecken Sie keine nachgelagerten Engpässe
Prefetch-Tuning wird keine langsame Datenbank reparieren. Es kann nur ändern, wie Arbeit verteilt und gepuffert wird. Wenn jede Nachricht auf dieselbe überlastete API wartet, kann ein höherer Prefetch den Durchsatz kurzzeitig besser aussehen lassen, während Timeouts und Wiederholungen zunehmen.
Messen Sie innerhalb des Consumers. Protokollieren oder geben Sie Metriken aus für die Zeit, die für das Dekodieren der Nachricht, das Warten auf die Datenbank, das Aufrufen externer Dienste und das Bestätigen aufgewendet wird. RabbitMQ kann Ihnen bereite und nicht bestätigte Zählungen zeigen, aber es kann Ihnen nicht sagen, warum Ihr Handler acht Sekunden braucht.
Wenn ein nachgelagerter Dienst ratenbegrenzt ist, sollte Prefetch oft niedriger, nicht höher sein. Lassen Sie die Queue den Rückstau sichtbar absorbieren, anstatt Tausende von laufenden Aufrufen in Workern zu verstecken.
Prefetch und Parallelität sind unterschiedlich
Ein Prefetch von 50 bedeutet nicht automatisch, dass Ihr Consumer 50 Nachrichten parallel verarbeitet. Es bedeutet nur, dass RabbitMQ 50 Nachrichten zustellen kann, bevor es Bestätigungen erhält. Ob sie gleichzeitig laufen, hängt von Ihrem Consumer-Code ab.
Ein Single-Threaded-Consumer mit Prefetch 50 kann eine Nachricht nach der anderen verarbeiten, während 49 im Speicher warten. Ein Worker-Pool mit Parallelität 10 und Prefetch 50 kann zehn aktive Aufgaben und vierzig gepufferte halten. Manchmal ist dieser Puffer nützlich. Manchmal ist es nur Latenz.
Passen Sie Prefetch an die tatsächliche Parallelität an. Wenn Ihr Prozess fünf Handler gleichzeitig ausführen kann, ist ein Prefetch von 5 bis 20 einfacher zu verstehen als 500.
Reihenfolge- und Fairness-Kompromisse
RabbitMQ-Queues bewahren die Reihenfolge auf Queue-Ebene, aber das Consumer-Verhalten kann die Reihenfolge ändern, in der Arbeiten abgeschlossen werden. Mit mehreren Consumern und Prefetch größer als 1 kann Nachricht 20 vor Nachricht 3 fertig sein, weil sie zu einem schnelleren Worker ging oder einfachere Arbeit hatte.
Für die meisten Arbeitsqueues spielt die Abschlussreihenfolge keine Rolle. Für Kontoupdates, Bestandsänderungen oder Workflows, die sequenziell verarbeitet werden müssen, kann sie sehr wichtig sein. In diesen Fällen kann die Verwendung einer Queue pro Ordnungsschlüssel, das Sharding nach Schlüssel oder das Halten eines niedrigen Prefetch sicherer sein, als den maximalen Durchsatz zu jagen.
Fairness hat einen ähnlichen Kompromiss. Ein niedriger Prefetch ermöglicht es RabbitMQ, Arbeit gleichmäßiger zu verteilen, weil Consumer häufiger für Nachrichten zurückkommen. Ein hoher Prefetch belohnt die Consumer, die zuerst Nachrichten erhalten. Wenn Nachrichten ungleiche Verarbeitungszeiten haben, kann dies dazu führen, dass ein Worker einen Haufen langsamer Jobs hält, während ein anderer Worker seinen Batch schnell beendet.
Wenn Leute sagen: „RabbitMQ-Lastverteilung ist ungleichmäßig“, ist Prefetch eines der ersten Dinge, die überprüft werden sollten. Der Broker kann nur Nachrichten ausgleichen, die noch nicht zugestellt wurden.
Fehlerverhalten ist wichtig
Prefetch ändert, was passiert, wenn ein Consumer stirbt. Mit prefetch_count=1 kommt eine nicht bestätigte Zustellung zurück, wenn der Kanal geschlossen wird. Mit prefetch_count=500 können Hunderte auf einmal zurückkommen. Wenn der Consumer vor dem Absturz teilweise Nebenwirkungen ausgeführt hat, können diese erneuten Zustellungen doppelte Schreibvorgänge, doppelte E-Mails oder doppelte API-Aufrufe auslösen, es sei denn, der Handler ist idempotent.
Das bedeutet nicht, dass hoher Prefetch falsch ist. Es bedeutet, dass hoher Prefetch zu idempotenten Handlern, klaren Wiederholungsregeln und Überwachung der Wiederholungsraten gehört. Wenn doppelte Verarbeitung gefährlich wäre, halten Sie das Fenster nicht bestätigter Nachrichten klein, bis die Anwendung dafür ausgelegt ist.
Betrachten Sie das redelivered-Flag im Consumer. Es ist kein vollständiger Wiederholungszähler, aber ein nützliches Signal, dass die Nachricht bereits zugestellt wurde. Für robuste Wiederholungsgrenzen verfolgen Sie Versuche in Headern oder im Anwendungszustand und leiten erschöpfte Nachrichten an eine Dead-Letter-Queue weiter.
Mehrere Queues und gemischte Workloads
Ein Prefetch-Wert passt selten für jede Queue. Ein Dienst, der thumbnail.generate und email.send konsumiert, benötigt möglicherweise unterschiedliche Einstellungen für jede. Die Thumbnail-Generierung kann CPU-intensiv sein und am besten mit niedriger Parallelität arbeiten. Das Senden von E-Mails kann netzwerkgebunden sein und mehr Nachrichten in der Luft tolerieren.
Wenn ein einzelner Prozess mehrere Queues auf einem Kanal konsumiert, kann das QoS-Verhalten schwerer zu verstehen sein. Bevorzugen Sie separate Kanäle für wesentlich unterschiedliche Workloads. Das macht Prefetch, Überwachung und Fehlerbehandlung offensichtlicher.
Gemischte Nachrichtengrößen sind ein weiteres Warnsignal. Wenn eine Queue sowohl winzige Ereignisse als auch riesige Payloads enthält, spiegelt ein zählbasierter Prefetch den Speicherdruck nicht gut wider. Zehn kleine Nachrichten und zehn große Nachrichten sind nicht die gleichen Kosten. Teilen Sie in dieser Situation den Workload auf oder verschieben Sie große Payloads aus RabbitMQ und übergeben Sie stattdessen Referenzen.
Beobachten Sie unbestätigte Nachrichten pro Consumer, nicht nur pro Queue
Eine unbestätigte Zählung auf Queue-Ebene sagt Ihnen, dass es unerledigte Arbeit gibt, kann aber Schieflage verbergen. Ein Consumer kann die meisten nicht bestätigten Nachrichten halten, während der Rest fast leer ist. Das deutet oft auf hohen Prefetch, ungleiche Nachrichtenkosten oder einen ungesunden Worker hin.
Verwenden Sie Consumer-Level-Metriken aus der Verwaltungsoberfläche, Prometheus oder rabbitmqctl list_consumers während eines Tests. Wenn die Verteilung ungleichmäßig ist, können die Senkung des Prefetch oder das Aufteilen langsamer Nachrichtentypen die tatsächliche Latenz verbessern, auch wenn sich der Gesamtdurchsatz nur wenig ändert.
Überprüfen Sie Prefetch nach Bereitstellungen
Prefetch-Werte altern. Ein Wert, der funktionierte, als ein Handler nur eine Datenbankzeile schrieb, kann nach der nächsten Version, die einen API-Aufruf, zusätzliche Validierung oder einen größeren Payload hinzufügt, falsch sein. Behandeln Sie Prefetch als Teil der Leistungskonfiguration, nicht als eine Zahl, die Sie einmal setzen und vergessen.
Vergleichen Sie nach einer Consumer-Version die Verarbeitungslatenz, unbestätigte Zählungen, erneute Zustellungen und den Consumer-Speicher mit der vorherigen Version. Wenn die Latenz steigt, aber die CPU nicht ausgelastet ist, wartet der Handler möglicherweise auf etwas Externes, und ein niedrigerer Prefetch kann das System fairer halten. Wenn die CPU hoch ist und jede Nachricht CPU-gebunden ist, kann das Hinzufügen von Workern oder die Reduzierung der Arbeit pro Nachricht wichtiger sein als die Änderung des Prefetch.
Dokumentieren Sie den Grund für den gewählten Wert in der Nähe der Consumer-Konfiguration. Zukünftige Betreuer sollten wissen, ob prefetch_count=5 aus Gründen der Fairness, des Speichers, der Reihenfolge, nachgelagerter Ratenbegrenzungen oder nur als temporärer Standard gewählt wurde.
Testen Sie mit echten Nachrichtenformen
Stimmen Sie Prefetch nicht mit winzigen gefälschten Nachrichten ab, wenn Produktionsnachrichten große JSON-Payloads sind oder teure Datenbankabfragen beinhalten. Nachrichtengröße und Handler-Kosten sind wichtig.
Eine nützliche Testschleife ist:
- Wählen Sie einen Prefetch-Wert.
- Führen Sie eine realistische Veröffentlichungsrate lange genug aus, um ein stetiges Verhalten zu sehen.
- Beobachten Sie
messages_ready,messages_unacknowledged, Consumer-CPU, Consumer-Speicher, Verarbeitungslatenz und Fehlerrate. - Töten Sie einen Consumer und sehen Sie, wie viele Nachrichten erneut zugestellt werden.
- Erhöhen oder verringern Sie den Prefetch und wiederholen Sie.
Der beste Wert ist selten der mit dem höchsten kurzfristigen Benchmark-Durchsatz. Es ist der Wert, der Consumer beschäftigt hält, die Latenz akzeptabel hält und auf eine Weise ausfällt, die Ihr System handhaben kann.
Ein praktischer Standard
Wenn Sie noch keine Daten haben, beginnen Sie mit manuellen Bestätigungen und prefetch_count=10 für gewöhnliche Arbeitsqueues. Verwenden Sie 1 für langsame, teure oder streng faire Verarbeitung. Versuchen Sie 20 oder 50 für schnelle, stabile Handler nach dem Messen. Gehen Sie nur höher, wenn Metriken zeigen, dass Zustellungs-Roundtrips der Engpass sind und Consumer Speicherreserven haben.
RabbitMQ-Prefetch-Tuning ist keine einmalige Einrichtung. Überprüfen Sie es erneut, wenn sich die Nachrichtengröße, der Consumer-Code, nachgelagerte Abhängigkeiten ändern oder Sie weitere Worker-Instanzen hinzufügen. Der richtige Prefetch-Wert ist der, der der aktuellen Form der Arbeit entspricht.