Zum Hauptinhalt springen

CooLWJ durchsuchen

选择搜索范围,输入关键词后按回车。

Sprache

rabbitmq-Konsumenten „verschwinden ohne ersichtlichen Grund“

Hintergrund des Problems:

Am Tag 1 des Monats 9 gegen 17:12 ging eine Beschwerde von Implementierungsmitarbeitern ein: Einige Geräte ließen sich nicht ordnungsgemäß aktualisieren, empfingen keine von der Konsole gesendeten Befehle und hatten weitere Probleme. Ein Kollege prüfte die Protokolle des control-Projekts(im Folgenden control)und stellte fest, dass control die von den Geräten gemeldeten Geräte-Schatteninformationen nicht erhalten und deshalb keine Befehle gesendet hatte. Das control-Projekt kommuniziert direkt mit den Geräten und sendet ihnen auf Grundlage ihrer Meldungen Befehle und Konfigurationsinformationen, darunter Anweisungen für Updates und die Übermittlung von Protokollen. Vor der Einführung von IoT bezog control die aktuellen Geräteinformationen aus Heartbeat-Meldungen; seit der Einführung von IoT stützt es sich auf Geräte-Schatteninformationen

um die aktuellen Geräteinformationen abzurufen, abonniert control die Schatteninformationen des Geräts. Diese werden jedoch vom Schattendienst (kurz IoT) weitergeleitet; control empfängt die vom Gerät gemeldeten Schatteninformationen nicht direkt. Die Einzelheiten des Ablaufs zeigen die folgenden beiden Abbildungen:

Die Anwendung (control) ruft den Gerätestatus ab

Die Anwendung(control)sendet Befehle an das Gerät

 

Als ich erfuhr, dass control keine Schattennachrichten empfing, schaute ich sofort in der rabbitmq-Konsole nach, ob Nachrichten vorhanden waren, um zwei Dinge zu klären:1.Ob das Gerät Nachrichten gemeldet hatte 2.Ob rabbitmq normal funktionierte. Die folgenden Abbildungen 1、2 sind zwei damals aufgenommene Bildschirmfotos der rabbitmq-Konsole, aus Abbildung 1 Es lässt sich eindeutig feststellen, dass das Gerät Nachrichten meldet, aber viele Nachrichten sind unacked(Das bedeutet, dass die Nachricht bereits an den Consumer zugestellt wurde, dieser aber noch kein ack gesendet hat. Theoretisch sollte nach einer gewissen Wartezeit wieder alles normal funktionieren). In welcher Warteschlange sich die unacked-Nachrichten angesammelt haben, sehen Sie unter Abbildung 2,“spacebridgeiot-shadow” ist genau diejenige, über die wir die von den Geräten gemeldeten Schatteninformationen empfangen. Da sich alle Nachrichten in der Warteschlange stauten, war es also nachvollziehbar, dass sie nicht an control weitergeleitet wurden. Nach einiger Beobachtungszeit sank die Anzahl von unacked auf 0, während sich die Gesamtzahl total kaum veränderte. Es wirkte, als seien die unacked-Nachrichten in die Nachrichtenwarteschlange zurückgekehrt und warteten dort erneut auf die Zustellung. Tatsächlich gab es einige Minuten später wieder sehr viele unacked-Nachrichten. Nach weiteren Minuten kehrten diese Nachrichten erneut in die Warteschlange zurück, während control weiterhin keine Nachrichten erhielt. Ein Blick in die Protokolle auf IoT-Seite zeigte dann überraschenderweise, dass gar keine Schattennachrichten eingingen. In der rabbitmq-Konsole wurde „spacebridgeiot-shadow” In dieser Warteschlange gibt es tatsächlich keine Konsumenten mehr, beispielsweise Abbildung 3 gezeigt. Ein Blick in die rabbitmq-Protokolle zeigt zu diesem Zeitpunkt tatsächlich Fehlermeldungen, wie Abbildung 4 wie gezeigt, hat rabbitmq die Verbindung aktiv geschlossen.

Abbildung 1:Übersicht über rabbitmq

 

 

Abbildung 2: Statistikdiagramm der rabbitmq-Warteschlange

 

 

Abbildung 3:Überblick über spacebridgeiot-shadow

 

 

 Abbildung 4:rabbitmq-Fehlermeldung

 

Vorläufige Lösung:

Da damals bereits zahlreiche Beschwerden eingegangen waren, griffen wir zu einer ziemlich radikalen Lösung: „die aufgestauten Nachrichten löschen“. Nach dem Löschen lief tatsächlich alles wieder normal(Anmerkung: Probleme im Produktivbetrieb müssen so schnell wie möglich gelöst werden. Es bleibt keine Zeit, erst die Protokolle zu analysieren und sie dann in aller Ruhe systematisch zu beheben; es muss schnell gehen)。

 

RabbitMQ-Technologiekennzeichen

Das Problem in einer lokalen Umgebung reproduzieren:

1.In der RabbitMQ-Warteschlange „mirrorTestQueue“ der Umgebung 10.200.41.166 eine große Menge an Nachrichten ansammeln(mindestens zehntausend)

2.Die Consumer von mirrorTestQueue stoppen und nach dem Aufstauen der Nachrichten erneut starten

3.Nach Abschluss des Nachrichtenstaus den Consumer neu starten

 

Wie von uns erwartet, wurden innerhalb weniger Sekunden Tausende Nachrichten an den Konsumenten gesendet. Nachdem dies einige Minuten angehalten hatte, schloss rabbitmq von sich aus die Verbindung zum Konsumenten. In der Konsole war daraufhin kein Konsument der Warteschlange mehr zu sehen. Da wir für unseren Konsumenten die automatische Wiederherstellung aktiviert hatten, verband er sich nach einer Weile wieder automatisch,

Doch kurz darauf wird die Verbindung wieder getrennt, ganz ähnlich wie bei dem Problem in unserer Produktionsumgebung. Was genau verursacht dieses Problem? Ehrlich gesagt hatte ich damals keine rechte Idee, und auch nach einigem Suchen im Netz fand ich keine wirklich zufriedenstellende Antwort(Damals hatte ich den Kern des Problems nicht erkannt; die verwendeten Suchbegriffe waren zu allgemein),später

Wir vermuteten ein Problem auf der TCP-Ebene und beschlossen deshalb, Pakete mitzuschneiden, um vielleicht einen Anhaltspunkt zu finden. Tatsächlich hatten wir Glück. Ohne weitere Umschweife direkt zum Bild.

 

 

 Vor 13:06:25.643428 lieferte rabbitmq weiterhin Nachrichten an den Consumer aus, bis ab 13:06:25.643428 das TCP-Fenster des Consumers vollständig ausgelastet war. Dieser Zustand hielt ungefähr 30 Sekunden an, dann trennte rabbitmq von sich aus die Verbindung(durch Senden eines RST-Pakets). Anschließend verband sich der Consumer erneut, woraufhin das Fenster wieder vollständig ausgelastet wurde und die Verbindung nach weiteren ungefähr 30 Sekunden erneut getrennt wurde.

 

Es wirkt ziemlich regelmäßig, jedes Mal dauert es 30s. Es scheint ein konfigurierbarer Parameter zu sein. Grob zusammengefasst:“Ein volles TCP-Fenster führt dazu, dass der Server die Verbindung aktiv per RST zurücksetzt, und das geschieht regelmäßig”

Diesmal habe ich die Suchbegriffe etwas geändert und es gefunden Antwort,RabbitMQ hat einen Parameter namens tcp_listen_options.send_timeout ist ein Parameter zur Steuerung des Schreibzeitlimits. Wird dieses überschritten, löst dies ein TCP-RST aus(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),ändern wir es etwas und sehen uns die Wirkung an:

1. Das Schreibzeitlimit auf 10s ändern

tcp_listen_options.send_timeout = 10000

 2.Mit einem Paketmitschnitt prüfen, ob es funktioniert

 

 Vom vollen Fenster bis zum Schließen der Verbindung vergehen ungefähr 10s.

 

Rückblick auf das beobachtete Verhalten:

Da auf der Verbraucherseite von rabbitmq kein prefetch eingestellt war, stellte rabbitmq dem Verbraucher auf einmal zu viele Nachrichten zu. Dadurch wurde dessen TCP-Fenster vollständig belegt, was wiederum tcp_listen_options.send_timeout von rabbitmq auslöste. Sobald diese Schreibzeitüberschreitung einen bestimmten Schwellenwert erreicht, trennt rabbitmq die TCP-Verbindung zum Verbraucher.

 

Die endgültige Lösung:

        Das vorherige Löschen von Nachrichten war lediglich eine Notlösung. Es löste zwar das Problem, war aber zu brachial. Wir brauchen eine elegante Lösung: Wenn zu viele an die Consumer gepushte Nachrichten das TCP-Fenster vollständig füllen, sollten wir bei der Empfangsrate ansetzen. Es genügt, RabbitMQ beim Verbindungsaufbau mitzuteilen, dass es uns nicht zu viele Nachrichten schicken soll.(Der folgende Abschnitt stammt aus https://blog.csdn.net/james_searcher/article/details/70308565) RabbitMQ besitzt eine Eigenschaft namens prefetch. prefetch bezeichnet die maximale Anzahl nicht bestätigter Nachrichten (unacked messages), die ein einzelner Consumer empfangen kann. Wie ist das zu verstehen? MQ richtet für jeden Consumer einen Puffer ein, dessen Größe prefetch entspricht. Bei jeder eingehenden Nachricht schiebt MQ diese in den Puffer und leitet sie anschließend an den Client weiter. Sobald eine ack-Nachricht eingeht (der Consumer sendet den Befehl baseack), gibt MQ einen Platz im Puffer frei und fügt eine neue Nachricht hinzu. Ist der Puffer jedoch voll, gerät MQ in einen blockierten Zustand. Konkreter gesagt: Angenommen, prefetch ist auf 10 gesetzt und es gibt zwei Consumer. Jeder Consumer holt dann jeweils 10 Nachrichten vorab aus der Queue und speichert sie lokal zwischen, bis sie verarbeitet werden. Gleichzeitig steigt die Anzahl der unacked-Nachrichten dieses Channels auf 20. Rabbit liefert dabei zuerst 10 Nachrichten an consumer1 aus und anschließend 10 Nachrichten an consumer2. Wenn nun eine neue Nachricht zugestellt werden soll, wird zuerst geprüft, ob die Anzahl der unacked-Nachrichten des Channels gleich 20 ist. Wenn ja, wird die Nachricht keinem Consumer zugestellt und bleibt in der Queue. Bestätigt danach einer der Consumer eine Nachricht mit ack, beträgt unacked nun 19. Rabbit prüft dann, bei welchem Consumer die Anzahl der unacked-Nachrichten unter 10 liegt, und stellt diesem die Nachricht zu.

 

Im Code sieht das konkret so aus:

 

 

Wie lässt sich dieser Wert bestimmen? Ein offizieller Artikel von RabbitMQ erklärt das sehr gut, deshalb gehe ich hier nicht näher darauf ein. In unserem System ist derzeit 20 eingestellt.

https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/

 

 

Schlusswort:

Beim Einsatz von rabbitmq stehen wir noch am Anfang. Vor der Verwendung sollten wir unbedingt die API von rabbitmq gründlich lesen, uns mit der Verwendung der gängigen APIs vertraut machen und in einer lokalen Testumgebung viel experimentieren.


Kommentare

Wird gesendet

Design auswählen

Das Design gilt für die gesamte Website.