Fehlerbehebung bei häufigen RabbitMQ-Konfigurationsproblemen

Finden und beheben Sie Fehler bei RabbitMQ-Austauschen, -Warteschlangen, -Bindungen, -Bestätigungen und -Berechtigungen, ohne falschen Fährten zu folgen.

Fehlerbehebung bei häufigen RabbitMQ-Konfigurationsproblemen

Die meisten RabbitMQ-Konfigurationsprobleme sehen zunächst wie Anwendungsfehler aus. Ein Publisher sagt, er habe die Nachricht gesendet. Ein Consumer sagt, er habe sie nie gesehen. Der Warteschlangen-Graph ist leer, oder schlimmer noch, er ist voll und niemand weiß warum. Der schnellste Weg aus dieser Situation ist, mit dem Raten aufzuhören und dem Nachrichtenpfad zu folgen: Publisher, Exchange, Binding, Queue, Consumer, Acknowledgement.

RabbitMQ ist streng in Bezug auf die Topologie. Ein Direct Exchange matcht nicht "ungefähr" einen Routing-Key. Eine als exclusive deklarierte Queue verhält sich nicht wie eine gemeinsame Arbeitswarteschlange. Eine als mandatory veröffentlichte Nachricht kann zurückgesendet werden, während dieselbe nicht routbare Nachricht ohne mandatory vom Exchange einfach verworfen werden kann. Diese Details sind klein, bis sie Sie einen Nachmittag kosten.

Beginnen Sie mit der tatsächlichen Route

Bei einer normalen AMQP-Veröffentlichung sendet der Producer eine Nachricht mit einem Routing-Key an einen Exchange. Der Exchange verwendet seinen Typ und seine Bindungen, um zu entscheiden, welche Queues die Nachricht erhalten sollen. Consumer ziehen dann Lieferungen aus den Queues und bestätigen sie nach der Verarbeitung.

Wenn eine Nachricht verschwindet, stellen Sie vier Fragen:

  • Hat der Producer an den Exchange und die virtuelle Host veröffentlicht, die Sie denken?
  • Existiert dieser Exchange und ist er der Typ, den Sie denken?
  • Gibt es eine Bindung von diesem Exchange zur beabsichtigten Queue?
  • Matcht der Routing-Key diese Bindung für den Exchange-Typ?

Das klingt einfach, aber es erfasst viele reale Vorfälle. Staging und Produktion verwenden oft unterschiedliche virtuelle Hosts. Ein Deployment-Skript kann in einer Umgebung orders.created und in einer anderen order.created deklarieren. Eine Queue kann an ein Topic-Muster gebunden sein, das ein zusätzliches Wort übersieht.

Verwenden Sie die Management-UI oder CLI, um den Live-Broker zu inspizieren, nicht den Code, von dem Sie hoffen, dass er läuft:

rabbitmqctl list_exchanges name type durable auto_delete
rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
rabbitmqctl list_queues name durable auto_delete exclusive messages_ready messages_unacknowledged consumers

Wenn Sie mehrere virtuelle Hosts verwenden, fügen Sie -p hinzu:

rabbitmqctl -p production list_bindings source_name destination_name routing_key

Routing-Key-Fehlanpassungen

Direct Exchanges erfordern eine exakte Übereinstimmung des Binding-Keys. Wenn eine Queue mit invoice.created gebunden ist, wird eine mit invoices.created veröffentlichte Nachricht nicht ankommen. RabbitMQ korrigiert keine Pluralisierung, Groß-/Kleinschreibung, Punkte oder Bindestriche.

Topic Exchanges verwenden * für ein Wort und # für null oder mehr Wörter. Das Worttrennzeichen ist ein Punkt. Eine Bindung von logs.* matcht logs.info, aber nicht logs.app.info. Eine Bindung von logs.# matcht beide.

Ein nützlicher Troubleshooting-Trick ist, eine temporäre Diagnose-Queue mit einer breiten Bindung hinzuzufügen und dann eine bekannte Testnachricht zu veröffentlichen:

rabbitmqadmin declare queue name=debug.routing durable=false auto_delete=true
rabbitmqadmin declare binding source=events destination=debug.routing destination_type=queue routing_key='#'

Machen Sie dies vorsichtig in der Produktion und entfernen Sie die Diagnose-Bindung, wenn Sie fertig sind. Das Ziel ist zu beweisen, ob Nachrichten überhaupt den Exchange erreichen.

Für wichtige Publisher aktivieren Sie Publisher Returns mit dem mandatory-Flag, damit nicht routbare Nachrichten für den Publisher sichtbar sind. Publisher Confirms sagen Ihnen, dass der Broker die Veröffentlichung akzeptiert hat; Returns sagen Ihnen, dass der Exchange sie nicht an eine Queue routen konnte. Sie beantworten unterschiedliche Fragen.

Fehler beim Exchange-Typ

Änderungen des Exchange-Typs sind eine häufige Quelle von Verwirrung, da die Deklaration eines vorhandenen Exchanges mit anderen Eigenschaften fehlschlägt. Wenn ein Dienst events als topic deklariert und ein anderer events als direct, sollte die zweite Deklaration einen Precondition-Fehler erhalten.

Dieser Fehler ist gut. Er verhindert, dass zwei Anwendungen stillschweigend über das Routing uneinig sind. Die Lösung ist nicht, die Ausnahme zu fangen und zu ignorieren. Die Lösung ist, die Topologie-Eigentümerschaft klar zu machen. Normalerweise sollte ein Deployment-Schritt oder ein Infrastruktur-Modul gemeinsame Exchanges und Queues deklarieren, während Anwendungen nur private Reply-Queues deklarieren oder idempotent die erwartete Topologie bestätigen.

Fanout Exchanges ignorieren Routing-Keys. Headers Exchanges routen nach Headern, nicht nach Routing-Keys. Wenn Ihre Testnachricht den richtigen Routing-Key hat, aber keine Queue sie erhält, überprüfen Sie den Exchange-Typ, bevor Sie jede Bindung bearbeiten.

Queue-Eigenschaften, die überraschen

Durable bedeutet, dass die Queue-Definition einen Broker-Neustart überlebt. Es bedeutet nicht, dass jede Nachricht in der Queue den Neustart überlebt. Damit Nachrichten einen Neustart überleben, muss die Queue durable sein und die Nachricht muss als persistent veröffentlicht werden. Selbst dann sollten Publisher Confirms verwenden, wenn sie wissen müssen, wann RabbitMQ die Nachricht sicher akzeptiert hat.

Auto-delete Queues werden entfernt, nachdem ihr letzter Consumer verschwunden ist. Sie sind nützlich für temporäre Abonnements, aber sie sind schlecht geeignet für gemeinsame Arbeitswarteschlangen. Exclusive Queues sind auf die Verbindung beschränkt, die sie deklariert, und verschwinden, wenn diese Verbindung geschlossen wird. Sie sind nützlich für Reply-Queues und private Consumer, nicht für mehrere Worker-Instanzen.

Wenn eine Queue "zufällig verschwindet", überprüfen Sie diese Flags:

rabbitmqctl list_queues name durable auto_delete exclusive consumers

Überprüfen Sie auch, ob der Anwendungscode die Queue beim Start mit anderen Argumenten deklariert als die vorhandene Queue. RabbitMQ behandelt Queue-Argumente wie Queue-Typ, Dead-Letter-Exchange, maximale Länge und einige haltbarkeitsbezogene Einstellungen als Teil des Deklarationsvertrags. Eine Nichtübereinstimmung kann den Kanal mit einem Precondition-Fehler schließen.

Nachrichten sind bereit, aber Consumer tun nichts

Wenn messages_ready hoch und consumers null ist, wartet RabbitMQ. Die Consumer-Anwendung ist möglicherweise ausgefallen, mit dem falschen virtuellen Host verbunden, verwendet den falschen Queue-Namen oder wird durch Berechtigungen blockiert.

Wenn Consumer verbunden sind, aber keine Lieferungen stattfinden, überprüfen Sie Prefetch und Consumer-Kapazität:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

Ein Consumer mit manuellen Bestätigungen und einem vollen Prefetch-Fenster erhält keine weiteren Nachrichten, bis er einige der bereits vorhandenen Nachrichten bestätigt oder ablehnt. Dies sieht oft so aus, als hätte RabbitMQ die Lieferung eingestellt, obwohl der Consumer tatsächlich unbestätigte Arbeit hält.

Wenn messages_unacknowledged hoch ist, schauen Sie sich die Consumer-Logs und nachgelagerten Systeme an. Eine langsame Datenbank, eine feststeckende HTTP-Abhängigkeit oder ein Handler, der Ausnahmen fängt, ohne zu bestätigen, können eine Wand von unbestätigten Nachrichten erzeugen.

Bestätigungsfehler

Manuelle Bestätigungen sind die normale Wahl für zuverlässige Verarbeitung. Der Consumer sollte erst bestätigen, nachdem die Arbeit abgeschlossen ist. Wenn sie fehlschlägt, sollte er ablehnen oder mit einer bewussten Entscheidung zum erneuten Einreihen nacken.

Das gefährliche Muster ist auto_ack=true für Arbeiten, die fehlschlagen können. Mit automatischen Bestätigungen betrachtet RabbitMQ die Nachricht als behandelt, sobald sie zugestellt wurde. Wenn der Consumer nach dem Empfang abstürzt, ist die Nachricht aus der Queue verschwunden.

Der gegenteilige Fehler ist, nie zu bestätigen. Der Consumer verarbeitet die Nachricht erfolgreich, schreibt vielleicht sogar in eine Datenbank, vergisst aber basic_ack. RabbitMQ behält die Lieferung als unbestätigt, bis der Kanal geschlossen wird, und liefert sie dann erneut. Das erzeugt doppelte Arbeit und wachsende unbestätigte Zählungen.

Eine einfache Handler-Struktur ist leichter zu prüfen:

def handle(ch, method, properties, body):
    try:
        process(body)
    except RetryableError:
        ch.basic_nack(method.delivery_tag, requeue=True)
    except Exception:
        ch.basic_nack(method.delivery_tag, requeue=False)
    else:
        ch.basic_ack(method.delivery_tag)

Wenn Sie jeden Fehler für immer erneut einreihen, kann eine schlechte Nachricht endlos schleifen. Verwenden Sie einen Dead-Letter-Exchange oder ein Retry-Design für Giftnachrichten.

Berechtigungen und virtuelle Hosts

RabbitMQ-Berechtigungen sind pro virtuellem Host begrenzt. Ein Benutzer kann möglicherweise eine Verbindung herstellen, aber dennoch keine Konfigurations-, Schreib- oder Leseberechtigungen für eine Queue oder einen Exchange haben. Dies kann sich als Kanalausnahme in den Client-Logs zeigen, nicht immer als freundlicher Anwendungsfehler.

Überprüfen Sie Berechtigungen direkt:

rabbitmqctl list_permissions -p production
rabbitmqctl list_user_permissions app_user

Für einen Dienst, der nur veröffentlicht, gewähren Sie Schreibberechtigungen für das benötigte Exchange-Muster und vermeiden Sie breite Konfigurationsrechte. Für einen Consumer gewähren Sie Leseberechtigungen für die Queue und Schreibberechtigungen, wenn er Nacks an Dead-Letter-Pfade veröffentlichen oder Reply-Muster verwenden muss. Zu breite Berechtigungen erleichtern die Fehlerbehebung heute und erschweren Sicherheitsüberprüfungen morgen.

Fehler bei der Dead-Letter-Konfiguration

Dead-Letter-Exchanges sollen Fehler sichtbar machen. Fehlkonfiguriertes Dead-Lettering bewirkt das Gegenteil: Nachrichten schlagen fehl, werden abgelehnt und verschwinden dann in einem Exchange, der keine Bindung hat.

Überprüfen Sie die Queue-Argumente, nicht nur den Queue-Namen:

rabbitmqctl list_queues name arguments

Für eine Queue, die fehlgeschlagene Jobs an Dead-Letter senden soll, sollten Sie Argumente wie x-dead-letter-exchange und manchmal x-dead-letter-routing-key sehen. Dann inspizieren Sie diesen Exchange und seine Bindungen genauso wie die Hauptroute.

Ein häufiger Fehler ist, einen Dead-Letter-Exchange namens jobs.dlx zu konfigurieren, aber die Dead-Letter-Queue an jobs.failed auf einem anderen Exchange zu binden. Ein anderer ist, x-dead-letter-routing-key auf einen Wert zu setzen, den keine Bindung matcht. RabbitMQ routet die Dead-Letter-Nachricht durch den Dead-Letter-Exchange wie jede andere Veröffentlichung. Wenn nichts matcht, hat die Nachricht keinen nützlichen Bestimmungsort.

Retry-Queues benötigen die gleiche Sorgfalt. Wenn Sie Retry mit TTL plus Dead-Lettering aufbauen, zeichnen Sie die Route auf Papier:

main queue -> reject -> retry exchange -> retry queue -> TTL expires -> main exchange -> main queue

Überprüfen Sie dann jeden Exchange, jede Queue, jede Bindung und jeden Routing-Key. Retry-Schleifen sind leicht versehentlich zu erstellen. Setzen Sie eine Obergrenze für Versuche in Nachrichten-Headern oder im Anwendungszustand, damit eine defekte Nutzlast nicht ewig rotiert.

Richtlinien-Überraschungen

Richtlinien können das Queue-Verhalten ändern, ohne dass der Anwendungscode es erwähnt. Eine Richtlinie kann Queue-Typ, maximale Länge, TTL, Dead-Letter-Exchange oder andere optionale Argumente setzen. Das ist nützlich für den Betrieb, kann aber die Fehlersuche verwirren, wenn sich eine Queue anders verhält als die Code-Deklaration.

Listen Sie Richtlinien während der Fehlerbehebung auf:

rabbitmqctl list_policies

Schauen Sie sich das Muster und die Priorität an. Eine breite Richtlinie wie .* kann Queues beeinflussen, die später von nicht verwandten Teams erstellt werden. Wenn eine Queue ältere Nachrichten verwirft, überprüfen Sie max-length oder Overflow-Einstellungen. Wenn Nachrichten früher als erwartet ablaufen, überprüfen Sie Queue-Level-TTL und Nachrichtenablauf.

Wenn die Anwendung einen Satz von Argumenten deklariert und eine Richtlinie einen anderen anwendet, hängen RabbitMQ-Regeln von der Einstellung ab. Einige optionale Argumente können durch Richtlinien gesteuert werden; andere müssen mit der Deklaration übereinstimmen. Die sichere Betriebsgewohnheit ist, das Queue-Verhalten an einem offensichtlichen Ort zu halten und jede Richtlinie zu dokumentieren, die absichtlich Anwendungsstandards überschreibt.

Wenn Producer und Consumer Topologie deklarieren

Viele Client-Bibliotheken machen es einfach, dass jeder Dienst beim Start Exchanges, Queues und Bindungen deklariert. Das kann in der Entwicklung praktisch sein. In der Produktion kann es Eigentumsprobleme schaffen.

Wenn sowohl Producer als auch Consumer dieselbe Queue deklarieren, müssen sie sich auf jede wichtige Eigenschaft einigen. Wenn eine Bereitstellung eine Queue von Auto-Delete zu Durable ändert oder ein Dead-Letter-Argument ändert, kann der nächste startende Dienst mit einem Precondition-Fehler fehlschlagen. Das ist besser als stille Abweichung, kann aber dennoch eine Bereitstellung unterbrechen.

Für gemeinsame Topologie bevorzugen Sie einen Eigentümer: Terraform, Ansible, einen Migrations-Job oder einen klar verantwortlichen Dienst. Der Anwendungsstart kann immer noch bestätigen, dass die erwartete Topologie existiert, aber er sollte nicht beiläufig gemeinsame Queues mit Standardwerten erstellen, die niemand überprüft hat.

Private Topologie ist anders. Ein Dienst, der eine temporäre Reply-Queue oder eine exklusive Abonnement-Queue erstellt, kann diese Queue direkt besitzen. Der Unterschied ist, ob ein anderer Dienst vom Queue-Namen und -Verhalten abhängt.

Halten Sie einen bekannten guten Veröffentlichungspfad

Für wichtige Systeme halten Sie einen winzigen Diagnose-Publisher oder ein Runbook-Kommando bereit, das eine harmlose Nachricht durch den erwarteten Exchange, Routing-Key und virtuellen Host sendet. Es sollte dieselbe Anmeldeinformationsklasse wie die echte Anwendung verwenden, oder zumindest einen Berechtigungssatz, der nah genug ist, um Routing- und Zugriffsprobleme zu erkennen.

Dieser bekannte gute Pfad ist während Bereitstellungen nützlich. Wenn die Diagnose-Nachricht geroutet wird, aber die Anwendungsnachricht nicht, vergleichen Sie den tatsächlichen Routing-Key, die Header und den virtuellen Host aus der App. Wenn die Diagnose-Nachricht ebenfalls fehlschlägt, ist das Problem wahrscheinlich Topologie, Berechtigungen oder Broker-Zustand.

Eine praktische Checkliste für Vorfälle

Wenn Nachrichten fehlen oder feststecken, sammeln Sie den Live-Zustand, bevor Sie alles neu starten:

rabbitmqctl -p production list_queues name messages_ready messages_unacknowledged consumers state
rabbitmqctl -p production list_bindings source_name destination_name routing_key
rabbitmqctl -p production list_exchanges name type durable
rabbitmqctl -p production list_connections name user vhost state

Senden Sie dann eine bekannte Testnachricht mit einer eindeutigen ID und verfolgen Sie sie durch die Logs. Testen Sie nicht mit einer zufälligen Produktionsnachricht, deren Pfad bereits unklar ist.

Die meisten RabbitMQ-Konfigurationsprobleme sind nicht mysteriös, sobald Sie die deklarierte Topologie mit dem Publisher- und Consumer-Verhalten in Einklang bringen. Der Broker tut normalerweise genau das, was ihm gesagt wurde. Die Arbeit besteht darin, den Ort zu finden, an dem das, was ihm gesagt wurde, von dem abweicht, was das Team meinte.