Udostępnij za pośrednictwem


Rozwiązywanie problemów z usługą Azure Service Bus

W tym artykule opisano techniki badania błędów, współbieżność, typowe błędy typów poświadczeń w bibliotece klienta Java usługi Azure Service Bus i kroki ograniczania ryzyka w celu rozwiązania tych błędów.

Włączanie i konfigurowanie rejestrowania

Zestaw Azure SDK dla języka Java oferuje spójny scenariusz rejestrowania, który pomaga w rozwiązywaniu problemów z błędami aplikacji i pomaga przyspieszyć ich rozwiązywanie. Dzienniki utworzone przechwytują przepływ aplikacji przed dotarciem do stanu terminalu, aby ułatwić zlokalizowanie głównego problemu. Aby uzyskać wskazówki dotyczące rejestrowania, zobacz Configure logging in the Azure SDK for Java oraz Troubleshooting overview.

Oprócz włączania rejestrowania ustawienie poziomu dziennika na VERBOSE lub DEBUG zapewnia wgląd w stan biblioteki. W poniższych sekcjach przedstawiono przykładowe konfiguracje log4j2 i logback, aby zmniejszyć nadmierne wiadomości przy włączonym szczegółowym logowaniu.

Konfigurowanie usługi Log4J 2

Aby skonfigurować usługę Log4J 2, wykonaj następujące kroki:

  1. Dodaj zależności w pom.xml przy użyciu tych z przykładu rejestrowania pom.xml, w sekcji "Zależności wymagane dla usługi Log4j2".
  2. Dodaj log4j2.xml do folderu src/main/resources.

Skonfiguruj logback

Aby skonfigurować rejestrowanie zwrotne, wykonaj następujące czynności:

  1. Dodaj zależności w pom.xml przy użyciu tych z przykładu rejestrowania pom.xmlw sekcji "Zależności wymagane dla logback".
  2. Dodaj logback.xml do folderu src/main/resources.

Włącz rejestrowanie transporu AMQP

Jeśli włączenie rejestrowania klienta nie wystarczy do zdiagnozowania problemów, możesz włączyć rejestrowanie do pliku w podstawowej bibliotece protokołu AMQP, Qpid Proton-J. Proton-J Qpid używa java.util.logging. Rejestrowanie można włączyć, tworząc plik konfiguracji z zawartością pokazaną w następnej sekcji. Możesz też ustawić proton.trace.level=ALL oraz dowolne opcje konfiguracji dla implementacji java.util.logging.Handler. Aby zapoznać się z klasami implementacji i ich opcjami, zobacz Package java.util.logging w dokumentacji zestawu JAVA 8 SDK.

Aby śledzić ramki transportu AMQP, ustaw zmienną środowiskową PN_TRACE_FRM=1.

Przykładowy plik logging.properties

Następujący plik konfiguracji rejestruje dane wyjściowe na poziomie TRACE z Proton-J do pliku 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

Zmniejszanie rejestrowania

Jednym ze sposobów zmniejszenia rejestrowania jest zmiana poziomu szczegółowości. Innym sposobem jest dodanie filtrów wykluczających logi z nazw pakietów rejestratorów, takich jak com.azure.messaging.servicebus lub com.azure.core.amqp. Aby zapoznać się z przykładami, zobacz pliki XML w sekcjach Konfigurowanie Log4J 2 i Konfigurowanie logback.

Gdy przesyłasz raport o błędzie, komunikaty dziennika z klas w następujących pakietach są istotne.

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • Wyjątkiem jest to, że można zignorować komunikat onDelivery w ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Współbieżność w ServiceBusProcessorClient

ServiceBusProcessorClient umożliwia aplikacji skonfigurowanie liczby wywołań programu obsługi komunikatów, które powinny być wykonywane współbieżnie. Ta konfiguracja umożliwia równoległe przetwarzanie wielu komunikatów. W przypadku ServiceBusProcessorClient pobierania komunikatów z jednostki niesesyjnej, aplikacja może skonfigurować żądaną współbieżność za pomocą interfejsu API maxConcurrentCalls. W przypadku jednostki z włączoną sesją żądana współbieżność jest maxConcurrentSessions razy maxConcurrentCalls.

Jeśli aplikacja obserwuje mniej współbieżnych wywołań programu obsługi komunikatów niż skonfigurowana współbieżność, może to być spowodowane tym, że pula wątków nie ma odpowiedniego rozmiaru.

ServiceBusProcessorClient używa wątków demona z globalnej boundedElastic puli wątków Elastic w celu wywołania procedury obsługi komunikatów. Maksymalna liczba współbieżnych wątków w tej puli jest ograniczona przez limit. Domyślnie ten limit wynosi dziesięć razy więcej niż liczba dostępnych rdzeni procesora CPU. Aby ServiceBusProcessorClient efektywnie obsługiwać wymaganą współbieżność aplikacji (maxConcurrentCalls lub maxConcurrentSessions razy maxConcurrentCalls), musisz mieć wartość limitu puli boundedElastic wyższą niż wymagana współbieżność. Domyślny limit można zastąpić, ustawiając właściwość system reactor.schedulers.defaultBoundedElasticSize.

Należy dostosować pulę wątków i alokację procesora CPU na podstawie indywidualnych potrzeb. Jednak kiedy przekraczasz limit puli, jako punkt wyjścia, ogranicz liczbę współbieżnych wątków do około 20–30 na każdy rdzeń procesora. Zalecamy ograniczenie żądanej współbieżności na instancję ServiceBusProcessorClient do w przybliżeniu 20–30. Zmierz i przeanalizuj swój konkretny przypadek użycia oraz odpowiednio dostosuj aspekty współbieżności. W przypadku scenariuszy wysokiego obciążenia rozważ uruchomienie wielu wystąpień ServiceBusProcessorClient, w których każde wystąpienie jest tworzone na podstawie nowego wystąpienia ServiceBusClientBuilder. Należy również rozważyć uruchomienie każdej ServiceBusProcessorClient na dedykowanym hoście — takim jak kontener lub maszyna wirtualna — aby przestój na jednym hoście nie wpływał na ogólne przetwarzanie komunikatów.

Należy pamiętać, że ustawienie wysokiej wartości limitu puli na hoście z kilkoma rdzeniami procesora miałoby niekorzystny wpływ. Niektóre oznaki niedoboru zasobów CPU lub puli z zbyt dużą liczbą wątków na mniejszej liczbie procesorów to: częste przekroczenia czasu, utrata blokady, zakleszczenie lub niższa wydajność. Jeśli używasz aplikacji Java w kontenerze, zalecamy użycie co najmniej dwóch rdzeni procesorów wirtualnych. Nie zalecamy wybierania mniej niż 1 rdzenia vCPU przy uruchamianiu aplikacji Java w środowiskach konteneryzowanych. Aby uzyskać szczegółowe zalecenia dotyczące zasobów, zobacz Konteneryzowanie aplikacji Java.

Wąskie gardło w udostępnianiu połączeń

Wszyscy klienci utworzeni na podstawie udostępnionego wystąpienia ServiceBusClientBuilder współużytkują to samo połączenie z przestrzeni nazw usługi Service Bus.

Użycie współdzielonego połączenia umożliwia operacje multipleksowania między klientami na jednej linii, ale współdzielenie może również stać się wąskim gardłem, jeśli jest wielu klientów lub jeśli klienci razem generują duże obciążenie. Każde połączenie ma skojarzony z nim wątek I/O. Podczas udostępniania połączenia klienci umieszczają swoje zadania w kolejce roboczej obsługiwanej przez współdzielony wątek wejścia/wyjścia, a postęp każdego klienta zależy od odpowiedniego przebiegu pracy w tej kolejce. Wątek I/O przetwarza zakolejkowane zadania szeregowo. Oznacza to, że jeśli kolejka zadań wątku wejścia/wyjścia połączenia udostępnionego ma dużo oczekującej pracy do wykonania, objawy są podobne do tych przy niskim obciążeniu procesora. Ten warunek został opisany w poprzedniej sekcji dotyczącej współbieżności — na przykład wstrzymanie klientów, przekroczenie limitu czasu, utrata blokady lub spowolnienie w ścieżce odzyskiwania.

SDK usługi Service Bus używa wzorca nazewnictwa reactor-executor-* dla wątku połączenia we/wy. Gdy aplikacja napotka wąskie gardło połączenia współużytkowanego, może się to odzwierciedlić w użyciu procesora wątku we/wy. Ponadto w zrzucie sterty lub w pamięci aktywnej obiekt ReactorDispatcher$workQueue jest kolejką roboczą wątku I/O. Długa kolejka zadań w migawce pamięci w okresie wąskiego gardła może wskazywać, że udostępniony wątek we/wy jest przeciążony oczekującymi zadaniami.

W związku z tym, jeśli obciążenie aplikacji w punkcie końcowym usługi Service Bus jest stosunkowo wysokie pod względem ogólnej liczby wysłanych i odebranych komunikatów lub rozmiaru payload, należy użyć oddzielnego wystąpienia konstruktora dla każdego utworzonego klienta. Na przykład dla każdej jednostki — kolejki lub tematu — można utworzyć nowy ServiceBusClientBuilder i zbudować klienta. W przypadku bardzo dużego obciążenia na określoną jednostkę można utworzyć wiele wystąpień klienta dla tej jednostki lub uruchomić klientów na wielu hostach — na przykład kontenerów lub maszyn wirtualnych — w celu równoważenia obciążenia.

Klienci zatrzymują się podczas korzystania z niestandardowego punktu końcowego usługi Application Gateway

Niestandardowy adres punktu końcowego odnosi się do adresu punktu końcowego HTTPS dostarczonego przez aplikację, który może być rozwiązywany do usługi Service Bus lub skonfigurowany do kierowania ruchu do usługi Service Bus. Usługa Azure Application Gateway ułatwia tworzenie frontonu HTTPS, który przekazuje ruch do usługi Service Bus. Można skonfigurować zestaw SDK usługi Service Bus dla aplikacji do użycia adresu IP warstwy frontowej usługi Application Gateway jako niestandardowego punktu końcowego do połączenia z usługą Service Bus.

Usługa Application Gateway oferuje kilka zasad zabezpieczeń obsługujących różne wersje protokołu TLS. Istnieją wstępnie zdefiniowane zasady wymuszające tlSv1.2 jako minimalną wersję, istnieją również stare zasady z TLSv1.0 jako minimalną wersją. Fronton HTTPS będzie miał zastosowane zasady protokołu TLS.

W tej chwili SDK usługi Service Bus nie rozpoznaje niektórych zdalnych zakończeń TCP przez front end usługi Application Gateway, który używa TLSv1.0 jako minimalnej wersji. Jeśli na przykład frontend wysyła pakiety TCP FIN i ACK, aby zamknąć połączenie po zaktualizowaniu jego właściwości, SDK nie może tego wykryć, więc nie połączy się ponownie, a klienci nie będą mogli już wysyłać ani odbierać komunikatów. Takie zatrzymanie odbywa się tylko w przypadku korzystania z tlsv1.0 jako minimalnej wersji. Aby rozwiązać ten problem, użyj zasad zabezpieczeń z protokołem TLSv1.2 lub nowszym jako minimalną wersją frontonu usługi Application Gateway.

Wsparcie dla tlSv1.0 i 1.1 we wszystkich usługach platformy Azure jest już ogłoszone do końca do 31 października 2024 r., dlatego przejście do TLSv1.2 jest zdecydowanie zalecane.

Utrata blokady komunikatu lub sesji

Kolejka usługi Service Bus lub subskrypcja tematu mają ustawiony czas trwania blokady na poziomie zasobu. Gdy klient odbiorcy ściąga komunikat z zasobu, broker usługi Service Bus stosuje początkową blokadę do komunikatu. Początkowa blokada trwa przez czas trwania blokady ustawiony na poziomie zasobu. Jeśli blokada komunikatu nie zostanie odnowiona przed wygaśnięciem, broker usługi Service Bus zwolni komunikat, aby udostępnić go innym odbiornikom. Jeśli aplikacja spróbuje ukończyć lub porzucić komunikat po wygaśnięciu blokady, wywołanie interfejsu API zakończy się niepowodzeniem z powodu błędu com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

Klient usługi Service Bus obsługuje uruchamianie zadania odnawiania blokady w tle, które stale odnawia blokadę komunikatu za każdym razem, zanim wygaśnie. Domyślnie zadanie odnawiania blokady jest uruchamiane przez 5 minut. Czas trwania odnawiania blokady można dostosować przy użyciu ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Jeśli przekażesz wartość Duration.ZERO, zadanie odnawiania blokady zostanie wyłączone.

Poniższa lista opisuje niektóre wzorce użycia lub środowiska hosta, które mogą prowadzić do błędu utraty blokady.

  • Zadanie odnawiania blokady jest wyłączone, a czas przetwarzania komunikatów aplikacji przekracza czas trwania blokady ustawiony na poziomie zasobu.

  • Czas przetwarzania komunikatów aplikacji przekracza skonfigurowany czas trwania zadania odnawiania blokady. Należy pamiętać, że jeśli czas trwania odnawiania blokady nie jest ustawiony jawnie, wartość domyślna to 5 minut.

  • Aplikacja włączyła funkcję Prefetch, ustawiając wartość prefetch na dodatnią liczbę całkowitą przy użyciu ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Po włączeniu funkcji Prefetch, klient pobierze liczbę komunikatów równą ustawieniu prefetch z elementu usługi Service Bus – kolejki lub tematu – i zapisze je w buforze Prefetch pamięci. Komunikaty pozostają w buforze pobierania wstępnego, dopóki nie zostaną przekazane do aplikacji. Klient nie rozszerza blokady komunikatów, gdy są w buforze pobierania wstępnego. Jeśli przetwarzanie aplikacji trwa tak długo, że blokada komunikatu wygasa podczas pozostawania w buforze pobierania wstępnego, aplikacja może uzyskać komunikaty z wygasłą blokadą. Aby uzyskać więcej informacji, zobacz Dlaczego Prefetch nie jest opcją domyślną?

  • W środowisku hosta występują sporadyczne problemy z siecią — na przykład przejściowe awarie sieci lub przerwa w działaniu — które uniemożliwiają odnowienie blokady na czas.

  • Środowisko hosta ma niewystarczającą liczbę CPU lub sporadyczne niedobory cykli CPU, co opóźnia zadanie odnowienia blokady.

  • Czas systemu hosta nie jest dokładny — na przykład zegar jest przesunięty — co opóźnia zadanie odnawiania blokady i uniemożliwia jego terminowe uruchomienie.

  • Wątek we/wy połączenia jest przeciążony, co ma wpływ na możliwość wykonywania blokady odnawiania wywołań sieciowych na czas. Następujące dwa scenariusze mogą powodować ten problem:

    • Aplikacja uruchamia zbyt wielu klientów odbiorników, którzy współdzielą to samo połączenie. Aby uzyskać dodatkowe informacje, zajrzyj do sekcji Wąskie gardło udostępniania połączeń.
    • Aplikacja skonfigurowała ServiceBusReceiverClient.receiveMessages lub ServiceBusProcessorClient, aby miały duże wartości maxMessages lub maxConcurrentCalls. Aby uzyskać więcej informacji, zobacz sekcję Współbieżność w ServiceBusProcessorClient.

Liczba zadań odnawiania blokady w kliencie jest równa wartościom parametrów maxMessages lub maxConcurrentCalls ustawionym dla ServiceBusProcessorClient lub ServiceBusReceiverClient.receiveMessages. Duża liczba zadań odnawiania blokady wykonujących wiele wywołań sieciowych może również mieć negatywny wpływ na ograniczanie przestrzeni nazw usługi Service Bus.

Jeśli host nie ma wystarczających zasobów, blokada nadal może zostać utracona, nawet jeśli działa tylko kilka zadań odnawiania blokady. Jeśli używasz aplikacji Java w kontenerze, zalecamy użycie co najmniej dwóch rdzeni procesorów wirtualnych. Nie zalecamy wybierania mniej niż 1 rdzenia procesora wirtualnego podczas uruchamiania aplikacji Java w środowiskach konteneryzowanych. Aby uzyskać szczegółowe zalecenia dotyczące zasobów, zobacz Konteneryzacja aplikacji Java.

Te same uwagi dotyczące blokad są również istotne dla kolejki usługi Service Bus lub subskrypcji tematu, która ma włączoną sesję. Gdy klient odbierający łączy się z sesją w zasobie, broker nakłada początkową blokadę na sesję. Aby zachować blokadę w sesji, zadanie odnawiania blokady w kliencie musi kontynuować odnawianie blokady sesji przed wygaśnięciem blokady. W przypadku zasobu z włączoną sesją podstawowe partycje czasami przemieszczają się, aby osiągnąć równoważenie obciążenia między węzłami Service Bus — na przykład, gdy dodawane są nowe węzły do dzielenia obciążenia. W takim przypadku blokady sesji mogą zostać utracone. Jeśli aplikacja próbuje ukończyć lub porzucić komunikat po utracie blokady sesji, wywołanie interfejsu API kończy się niepowodzeniem z błędem com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Uaktualnij do wersji 7.15.x lub najnowszej

Jeśli wystąpią jakiekolwiek problemy, najpierw spróbuj je rozwiązać, uaktualniając zestaw SDK usługi Service Bus do najnowszej wersji. Wersja 7.15.x to znaczące przeprojektowanie, które rozwiązuje trwające od dawna problemy z wydajnością i niezawodnością.

Wersja 7.15.x lub nowsza zmniejsza przeskoki wątków, usuwa blokady, optymalizuje kod w ścieżkach gorących i zmniejsza alokację pamięci. Te zmiany powodują do 45–50 razy większą przepływność w ServiceBusProcessorClient.

Wersja 7.15.x i nowsze są również wyposażone w różne ulepszenia niezawodności. Dotyczy to kilku warunków wyścigu (takich jak pobieranie wstępne i obliczenia kredytowe) i ulepszona obsługa błędów. Te zmiany powodują lepszą niezawodność w obecności przejściowych problemów w różnych typach klientów.

Korzystanie z najnowszych klientów

Nowa podstawowa struktura z tymi ulepszeniami — w wersji 7.15.x lub nowszej — nosi nazwę V2-Stack. Linia wydań zawiera zarówno poprzednią generację bazowego stosu — stosu używanego w wersji 7.14.x — oraz nowy stos V2.

Domyślnie niektóre typy klientów używają stosu V2-Stack, podczas gdy inne wymagają opcji V2-Stack. Dla typu klienta można zaakceptować lub odrzucić określony stos (V2 lub poprzedniej generacji), poprzez podanie wartości com.azure.core.util.Configuration podczas tworzenia klienta.

Na przykład sesja oparte na V2-Stack z ServiceBusSessionReceiverClient wymaga zgody, jak pokazano w poniższym przykładzie:

ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder()
    .connectionString(Config.CONNECTION_STRING)
    .configuration(new com.azure.core.util.ConfigurationBuilder()
        .putProperty("com.azure.messaging.servicebus.session.syncReceive.v2", "true") // 'false' by default, opt-in for V2-Stack.
        .build())
    .sessionReceiver()
    .queueName(Config.QUEUE_NAME)
    .buildClient();

W poniższej tabeli wymieniono typy klientów i odpowiadające im nazwy konfiguracji oraz wskazuje, czy klient jest aktualnie włączony domyślnie do używania V2-Stack w najnowszej wersji 7.17.0. W przypadku klienta, który nie znajduje się w V2-Stack domyślnie, możesz użyć przykładu pokazanego po prostu, aby wyrazić zgodę.

Typ klienta Nazwa konfiguracji Czy V2-Stack jest domyślnie włączone?
Klient nadawczy i zarządzający com.azure.messaging.servicebus.sendAndManageRules.v2 tak
Klient odbiornika procesora niesesyjnego i reaktora com.azure.messaging.servicebus.nonSession.asyncReceive.v2 tak
Klient odbiornika procesora sesji com.azure.messaging.servicebus.session.processor.asyncReceive.v2 tak
Klient odbiornika reaktora sesyjnego com.azure.messaging.servicebus.session.reactor.asyncReceive.v2 tak
Klient synchronicznego odbioru bez sesji com.azure.messaging.servicebus.nonSession.syncReceive.v2 Nie
Synchroniczny klient odbioru sesji com.azure.messaging.servicebus.session.syncReceive.v2 Nie

Alternatywą dla korzystania z com.azure.core.util.Configurationjest możliwość wyrażenia zgody lub rezygnacji, ustawiając te same nazwy konfiguracji przy użyciu zmiennych środowiskowych lub właściwości systemowych.

Następne kroki

Jeśli wskazówki dotyczące rozwiązywania problemów w tym artykule nie pomogą rozwiązać problemów podczas korzystania z bibliotek klienckich zestawu Azure SDK dla języka Java, zalecamy, aby zgłosić problem w repozytorium Azure SDK for Java w usłudze GitHub.