Freigeben über


Fehlerbehebung für Azure Servicebus

In diesem Artikel werden Fehleruntersuchungstechniken, Parallelität, häufige Fehler für die Anmeldeinformationstypen in der Azure Service Bus Java-Clientbibliothek sowie Schritte zur Behebung dieser Fehler behandelt.

Aktivieren und Konfigurieren der Protokollierung

Das Azure SDK für Java bietet ein einheitliches Protokollierungskonzept, das bei der Fehlerbehebung von Anwendungen unterstützt und die Lösung von Problemen beschleunigt. Die erstellten Protokolle erfassen den Ablauf einer Anwendung, bevor sie den Terminalstatus erreicht, um das Stammproblem zu finden. Richtlinien zur Protokollierung finden Sie unter Konfiguration der Protokollierung im Azure SDK für Java und Übersicht zur Fehlerbehebung.

Zusätzlich zur Aktivierung der Protokollierung bietet das Festlegen der Protokollebene auf VERBOSE oder DEBUG Einblicke in den Zustand der Bibliothek. In den folgenden Abschnitten finden Sie Beispiele für die Konfiguration von log4j2 und logback, um die übermäßigen Meldungen zu reduzieren, wenn die ausführliche Protokollierung aktiviert ist.

Konfigurieren von Log4J 2

Führen Sie die folgenden Schritte aus, um Log4J 2 zu konfigurieren:

  1. Fügen Sie die Abhängigkeiten in Ihrer pom.xml hinzu, indem Sie die Abhängigkeiten aus der Logging-Beispiel-pom.xml im Abschnitt „Für Log4j2 erforderliche Abhängigkeiten“ verwenden.
  2. Fügen Sie log4j2.xml zu Ihrem src/main/resources-Ordner hinzu.

Logback konfigurieren

Führen Sie die folgenden Schritte aus, um Logback zu konfigurieren:

  1. Fügen Sie die Abhängigkeiten in Ihrer pom.xml hinzu, indem Sie die Abhängigkeiten aus der pom.xml des Logging-Beispiels verwenden, und zwar im Abschnitt „Für Logback erforderliche Abhängigkeiten“.
  2. Fügen Sie logback.xml zu Ihrem src/main/resources-Ordner hinzu.

Aktivierung der AMQP-Transportprotokollierung

Wenn die Aktivierung der Client-Protokollierung nicht ausreicht, um Ihre Probleme zu diagnostizieren, können Sie die Protokollierung in eine Datei in der zugrunde liegenden AMQP-Bibliothek, Qpid Proton-J, aktivieren. Qpid Proton-J verwendet java.util.logging. Sie können die Protokollierung aktivieren, indem Sie eine Konfigurationsdatei mit den im nächsten Abschnitt gezeigten Inhalten erstellen. Oder Sie setzen proton.trace.level=ALL und die gewünschten Konfigurationsoptionen für die java.util.logging.Handler-Implementierung. Die Implementierungsklassen und deren Optionen finden Sie in der Java 8 SDK-Dokumentation unter Package java.util.logging.

Um die Ablaufverfolgung der AMQP-Transportrahmen durchzuführen, setzen Sie die Umgebungsvariable PN_TRACE_FRM=1.

Beispiel für die Datei logging.properties

Die folgende Konfigurationsdatei protokolliert die Ablaufverfolgung auf TRACE-Ebene von Proton-J in der Datei proton-trace.log:

handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n

Protokollierung reduzieren

Eine Möglichkeit, die Protokollierung zu verringern, besteht darin, die Ausführlichkeit zu ändern. Eine andere Möglichkeit ist das Hinzufügen von Filtern, die Protokolle von Paketen mit Loggernamen wie com.azure.messaging.servicebus oder com.azure.core.amqp ausschließen. Beispiele finden Sie in den XML-Dateien in den Abschnitten Konfiguration von Log4J 2 und Konfiguration von logback.

Wenn Sie einen Fehler übermitteln, sind die Protokollmeldungen aus Klassen in den folgenden Paketen interessant:

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • Die Ausnahme ist, dass Sie die Nachricht onDelivery in ReceiveLinkHandler ignorieren können.
  • com.azure.messaging.servicebus.implementation

Gleichzeitigkeit im ServiceBusProcessorClient

ServiceBusProcessorClient ermöglicht der Anwendung zu konfigurieren, wie viele Aufrufe des Nachrichtenhandlers gleichzeitig ausgeführt werden sollen. Diese Konfiguration ermöglicht es, mehrere Nachrichten parallel zu verarbeiten. Für eine ServiceBusProcessorClient, die Nachrichten von einer Nicht-Session-Entität konsumiert, kann die Anwendung die gewünschte Gleichzeitigkeit mithilfe der maxConcurrentCalls-API konfigurieren. Für eine sitzungsfähige Entität ist die gewünschte Gleichzeitigkeit maxConcurrentSessions mal maxConcurrentCalls.

Wenn die Anwendung weniger gleichzeitige Aufrufe des Nachrichtenverarbeiters als die konfigurierte Parallelität beobachtet, liegt dies möglicherweise daran, dass der Threadpool nicht entsprechend dimensioniert ist.

ServiceBusProcessorClient verwendet Daemon-Threads aus dem globalen BoundedElastic-Thread-Pool von Reactor, um den Message-Handler aufzurufen. Die maximale Anzahl gleichzeitiger Threads in diesem Pool ist durch eine Obergrenze begrenzt. Diese Obergrenze beträgt standardmäßig zehn mal die Anzahl der verfügbaren CPU-Kerne. Damit ServiceBusProcessorClient die von der Anwendung gewünschte Gleichzeitigkeit (maxConcurrentCalls- oder maxConcurrentSessions-mal maxConcurrentCalls) effektiv unterstützen kann, müssen Sie einen boundedElastic pool cap-Wert haben, der höher ist als die gewünschte Gleichzeitigkeit. Sie können die Standardbegrenzung überschreiben, indem Sie die Systemeigenschaft reactor.schedulers.defaultBoundedElasticSizefestlegen.

Sie sollten den Threadpool und die CPU-Zuordnung fallweise optimieren. Wenn Sie jedoch die Poolbegrenzung außer Kraft setzen, beschränken Sie als Ausgangspunkt die gleichzeitigen Threads auf ca. 20-30 pro CPU-Kern. Wir empfehlen Ihnen, die gewünschte Gleichzeitigkeit pro ServiceBusProcessorClient-Instanz auf etwa 20 bis 30 zu begrenzen. Analysieren und messen Sie Ihren spezifischen Anwendungsfall, und optimieren Sie die Aspekte der Gleichzeitigkeit entsprechend. Bei Szenarien mit hoher Auslastung sollten Sie mehrere ServiceBusProcessorClient Instanzen ausführen, in denen jede Instanz aus einer neuen ServiceBusClientBuilder Instanz erstellt wird. Erwägen Sie außerdem, jeden ServiceBusProcessorClient in einem dedizierten Host auszuführen, beispielsweise in einem Container oder einer VM, damit die Ausfallzeiten eines Hosts die gesamte Nachrichtenverarbeitung nicht beeinträchtigen.

Beachten Sie, dass das Festlegen eines hohen Werts für die Poolobergrenze auf einem Host mit wenigen CPU-Kernen nachteilige Auswirkungen haben würde. Einige Anzeichen für geringe CPU-Ressourcen oder einen Pool mit zu vielen Threads auf weniger CPUs sind: häufige Timeouts, verlorene Sperren, Deadlocks oder geringerer Durchsatz. Wenn Sie die Java-Anwendung auf einem Container ausführen, empfehlen wir die Verwendung von zwei oder mehr vCPU-Kernen. Es wird nicht empfohlen, weniger als 1 vCPU-Kern auszuwählen, wenn die Java-Anwendungen in containerisierten Umgebungen betrieben werden. Ausführliche Empfehlungen zur Ressourcenausstattung finden Sie unter Containerisierung Ihrer Java-Anwendungen.

Engpass bei der gemeinsamen Nutzung von Verbindungen

Alle Clients, die aus einer freigegebenen ServiceBusClientBuilder Instanz erstellt wurden, verwenden dieselbe Verbindung mit dem Service Bus-Namespace.

Die Verwendung einer gemeinsam genutzten Verbindung ermöglicht das Multiplexen von Operationen zwischen Clients auf einer Verbindung, aber die gemeinsame Nutzung kann auch zu einem Engpass werden, wenn es viele Clients gibt oder die Clients zusammen eine hohe Last erzeugen. Jeder Verbindung ist ein E/A-Thread zugeordnet. Bei der gemeinsamen Nutzung einer Verbindung stellen die Clients ihre Arbeit in die Work-Queue dieses gemeinsamen E/A-Threads und der Fortschritt jedes Clients hängt von der rechtzeitigen Fertigstellung seiner Arbeit in der Warteschlange ab. Der E/A-Thread bearbeitet die in der Warteschlange stehende Arbeit seriell. Das heißt, wenn die E/A-Thread-Work-Queue einer gemeinsam genutzten Verbindung eine Menge anstehender Arbeit zu bewältigen hat, dann sind die Symptome ähnlich wie bei einer niedrigen CPU. Diese Bedingung wird im vorherigen Abschnitt über Gleichzeitigkeit beschrieben – z. B. Clients, die blockieren, Timeout, verlorene Sperre oder Verlangsamung des Wiederherstellungspfads.

Service Bus SDK verwendet das reactor-executor-*-Namensmuster für den Verbindungs-E/A-Thread. Wenn die Anwendung einen Engpass bei der gemeinsamen Verbindung hat, kann sich dies in der CPU-Auslastung des E/A-Threads niederschlagen. Außerdem ist das Objekt ReactorDispatcher$workQueue im Heap-Dump oder im Live-Speicher die Work-Queue des I/O-Threads. Ein langer Work-Queue im Speicher-Snapshot während der Engpass-Periode könnte darauf hinweisen, dass der Shared I/O-Thread mit anstehenden Arbeiten überlastet ist.

Wenn die Anwendungslast für einen Servicebus-Endpunkt im Hinblick auf die Gesamtzahl der gesendeten und empfangenen Nachrichten oder die Größe der Nutzdaten relativ hoch ist, sollten Sie daher für jeden Client, den Sie erstellen, eine eigene Builder-Instanz verwenden. Zum Beispiel können Sie für jede Entität – Queue oder Thema – ein neues ServiceBusClientBuilder erstellen und daraus einen Client bauen. Bei extrem hoher Auslastung einer bestimmten Entität können Sie entweder mehrere Clientinstanzen für diese Entität erstellen oder Clients auf mehreren Hosts ausführen, z. B. in Containern oder VMs, um den Lastenausgleich durchzuführen.

Clients bleiben bei Verwendung des benutzerdefinierten Endpunkts von Application Gateway stehen

Die benutzerdefinierte Endpunktadresse bezieht sich auf eine von der Anwendung bereitgestellte HTTPS-Endpunktadresse, die zu Servicebus aufgelöst werden kann oder so konfiguriert ist, dass der Datenverkehr zu Servicebus geleitet wird. Das Azure-Anwendungsgateway erleichtert das Erstellen eines HTTPS-Front-Ends, das Datenverkehr an Service Bus weiterleitet. Sie können das Service Bus SDK für eine Anwendung so konfigurieren, dass die Front-End-IP-Adresse eines Application Gateway als benutzerdefinierter Endpunkt zum Verbinden mit dem Service Bus verwendet wird.

Das Anwendungsgateway bietet mehrere Sicherheitsrichtlinien, die unterschiedliche TLS-Protokollversionen unterstützen. Es gibt vordefinierte Richtlinien, die TLSv1.2 als Mindestversion erzwingen, es gibt auch alte Richtlinien mit TLSv1.0 als Mindestversion. Auf das HTTPS-Front-End wird eine TLS-Richtlinie angewendet.

Im Moment erkennt das Servicebus SDK bestimmte Remote-TCP-Terminierungen durch das Application Gateway-Frontend, das TLSv1.0 als Mindestversion verwendet, nicht. Wenn das Frontend beispielsweise TCP FIN- und ACK-Pakete sendet, um die Verbindung zu schließen, wenn seine Eigenschaften aktualisiert werden, kann das SDK dies nicht erkennen, so dass die Verbindung nicht wiederhergestellt werden kann und die Clients keine Nachrichten mehr senden oder empfangen können. Eine solche Unterbrechung geschieht nur, wenn TLSv1.0 als Mindestversion verwendet wird. Um dem entgegenzuwirken, setzen Sie eine Sicherheitsrichtlinie ein, bei der TLSv1.2 oder höher als Mindestversion für das Frontend des Anwendungsgateways verwendet wird.

Es ist bereits angekündigt, dass die Unterstützung für TLSv1.0 und 1.1 in allen Azure-Diensten zum 31. Oktober 2024 ausläuft. Es wird daher dringend empfohlen, auf TLSv1.2 umzustellen.

Nachricht oder Sitzungssperre ist verloren gegangen

Ein Servicebus-Queue oder ein Themenabonnement hat eine auf Ressourcenebene festgelegte Sperrdauer. Wenn der Empfänger-Client eine Nachricht aus der Ressource abruft, wendet der Service-Bus-Broker eine erste Sperre auf die Nachricht an. Die anfängliche Sperre hält für die auf der Ressourcenebene eingestellte Sperrdauer an. Wenn die Nachrichtensperre nicht verlängert wird, bevor sie abläuft, gibt der Service Bus-Broker die Nachricht frei, um sie für andere Empfänger verfügbar zu machen. Wenn die Anwendung versucht, eine Nachricht nach ablauf der Sperre abzuschließen oder aufzugeben, schlägt der API-Aufruf mit dem Fehler com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queuefehl.

Der Service Bus-Client unterstützt die Ausführung einer Aufgabe zur Erneuerung der Sperre im Hintergrund, die die Nachrichtensperre jedes Mal erneuert, bevor sie abläuft. Standardmäßig wird die Sperrenverlängerungsaufgabe 5 Minuten lang ausgeführt. Sie können die Dauer der Sperrverlängerung mit ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration) anpassen. Wenn Sie den Wert Duration.ZERO angeben, wird die Aufgabe Sperren erneuern deaktiviert.

Die folgenden Listen beschreiben einige der Verwendungsmuster oder Hostumgebungen, die zu dem Fehler „Sperre verloren“ führen können:

  • Der Vorgang zum Verlängern der Sperre ist deaktiviert, und die Nachrichtenverarbeitungszeit der Anwendung überschreitet die auf Ressourcenebene festgelegte Sperrdauer.

  • Die Nachrichtenverarbeitungszeit der Anwendung überschreitet die konfigurierte Dauer der Sperre der Aufgabe. Beachten Sie, dass, wenn die Verlängerungsdauer der Sperre nicht explizit festgelegt ist, sie standardmäßig auf 5 Minuten gesetzt wird.

  • Die Anwendung hat die Prefetch-Funktion aktiviert, indem sie den Prefetch-Wert mit ServiceBusReceiverClientBuilder.prefetchCount(prefetch) auf eine positive Ganzzahl gesetzt hat. Wenn die Prefetch-Funktion aktiviert ist, ruft der Client die Anzahl der Nachrichten, die dem Prefetch entspricht, von der Servicebus Entität – Warteschlange oder Thema – ab und speichert sie im speicherinternen Prefetch-Puffer. Die Nachrichten bleiben im Prefetch-Puffer, bis sie in der Anwendung empfangen werden. Der Client verlängert die Sperre der Nachrichten nicht, solange sie sich im Prefetch-Puffer befinden. Wenn die Verarbeitung der Anwendung so lange dauert, dass die Sperren der Nachrichten ablaufen, während sie im Prefetch-Puffer verbleiben, kann die Anwendung die Nachrichten mit einer abgelaufenen Sperre erfassen. Weitere Informationen finden Sie unter Warum ist Prefetch nicht die Standardoption?

  • In der Hostumgebung treten gelegentlich Netzwerkprobleme auf – z. B. ein vorübergehender Netzwerkausfall –, die verhindern, dass die Aufgabe zur Sperrenerneuerung die Sperre rechtzeitig erneuert.

  • In der Hostumgebung fehlen genügend CPUs oder es mangelt zeitweise an CPU-Zyklen, was den Vorgang zur Erneuerung der Sperre daran hindert, rechtzeitig ausgeführt zu werden.

  • Die Zeit des Hostsystems ist nicht genau – die Uhr geht zum beispielsweise nicht richtig. Dadurch verzögert sich die Sperraufgabe und kann nicht pünktlich ausgeführt werden.

  • Der Verbindungs-E/A-Thread ist überlastet, was sich auf seine Fähigkeit auswirkt, Anfragen zur Sperrenerneuerung im Netzwerk rechtzeitig auszuführen. Die folgenden beiden Szenarien können dieses Problem verursachen:

  • Ein gängiges Anwendungsmuster, das die Wahrscheinlichkeit eines verloren gegangenen Sperrfehlers erhöht, umfasst die Planung lang ausgeführter Sperrverlängerungsaufgaben – z. B. Vorgänge mit Dauer, die mehrere Stunden umfassen. Wie bereits erwähnt, können verschiedene Faktoren außerhalb der Kontrolle eines Service Bus-Clients eine erfolgreiche Sperrverlängerung beeinträchtigen, sodass Anwendungsdesigns eine garantierte Verlängerung über längere Zeiträume vermeiden sollten. Um zu vermeiden, dass lange ausgeführte Vorgänge erneut verarbeitet werden müssen, sollten Sie die Arbeit in kleinere Blöcke aufteilen oder idempotente Prüfpunktlogik implementieren.

Die Anzahl der Sperrenerneuerungsaufgaben im Client entspricht den maxMessages- oder maxConcurrentCalls-Parameterwerten, die für ServiceBusProcessorClient oder ServiceBusReceiverClient.receiveMessages festgelegt wurden. Eine hohe Anzahl von Tasks zur Sperrenerneuerung, die mehrere Anfragen an das Netzwerk stellen, kann sich auch negativ auf die Drosselung des Servicebus-Namespace auswirken.

Wenn der Host nicht über genügend Ressourcen verfügt, kann die Sperre verloren gehen, auch wenn nur wenige Aufgaben zur Erneuerung der Sperre laufen. Wenn Sie die Java-Anwendung auf einem Container ausführen, empfehlen wir die Verwendung von zwei oder mehr vCPU-Kernen. Es wird nicht empfohlen, etwas weniger als 1 vCPU-Kern auszuwählen, wenn Java-Anwendungen in containerisierten Umgebungen ausgeführt werden. Ausführliche Empfehlungen zur Ressourcenausstattung finden Sie unter Containerisierung Ihrer Java-Anwendungen.

Die gleichen Anmerkungen zu Sperren gelten auch für eine Service-Bus-Warteschlange oder ein Themenabonnement, für das eine Sitzung aktiviert ist. Wenn der Empfänger-Client eine Verbindung zu einer Sitzung in der Ressource herstellt, wendet der Broker eine erste Sperre auf die Sitzung an. Um die Sitzungssperre beizubehalten, muss der Prozess zum Erneuern der Sperre im Client die Sitzungssperre ständig erneuern, bevor sie abläuft. Bei einer sitzungsfähigen Ressource werden die zugrundeliegenden Partitionen manchmal verschoben, um einen Lastausgleich zwischen den Servicebus-Knoten zu erreichen – beispielsweise, wenn neue Knoten hinzugefügt werden, um die Last zu verteilen. Wenn das passiert, können Sitzungssperren verloren gehen. Wenn die Anwendung versucht, eine Nachricht nach dem Verlust der Sitzungssperre abzuschließen oder aufzugeben, schlägt der API-Aufruf mit dem Fehler com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiverfehl.

Nächste Schritte

Wenn die Richtlinien zur Fehlerbehebung in diesem Artikel nicht helfen, Probleme bei der Verwendung der Azure SDK for Java Client-Bibliotheken zu lösen, empfehlen wir Ihnen, einen Fehler im Azure SDK for Java GitHub Repository zu melden.