Condividi tramite


Risolvere i problemi relativi al bus di servizio di Azure

Questo articolo illustra le tecniche di analisi degli errori, la concorrenza, gli errori comuni per i tipi di credenziali nella libreria client Java del bus di servizio di Azure e i passaggi di mitigazione per risolvere questi errori.

Abilitare e configurare la registrazione

Azure SDK per Java offre una storia di registrazione coerente che consente di risolvere gli errori delle applicazioni e di accelerare la risoluzione. I log generati acquisiscono il flusso di un'applicazione prima di raggiungere lo stato terminale per individuare la causa principale del problema. Per indicazioni sulla registrazione, vedere Configurare la registrazione in Azure SDK per Java e Panoramica della risoluzione dei problemi.

Oltre ad abilitare la registrazione, impostare il livello di log su VERBOSE o DEBUG fornisce informazioni dettagliate sullo stato della libreria. Le sezioni seguenti mostrano configurazioni di esempio per log4j2 e logback al fine di ridurre i messaggi eccessivi quando è abilitata la registrazione dettagliata.

Configurare Log4J 2

Per configurare Log4J 2, seguire questa procedura:

  1. Aggiungere le dipendenze nel pom.xml usando quelle dell'esempio di registrazione pom.xml, nella sezione "Dipendenze necessarie per Log4j2".
  2. Aggiungi log4j2.xml alla cartella src/main/resources .

Configurare il logback

Per configurare il logback, seguire questa procedura:

  1. Aggiungere le dipendenze nel pom.xml usando quelle dell'esempio di registrazione pom.xml, nella sezione "Dipendenze necessarie per il logback".
  2. Aggiungere logback.xml alla cartella src/main/resources .

Abilitare la registrazione del trasporto AMQP

Se l'abilitazione della registrazione dei log del client non è sufficiente per diagnosticare i tuoi problemi, puoi abilitare la registrazione su un file nella libreria AMQP sottostante, Qpid Proton-J. Qpid Proton-J usa java.util.logging. È possibile abilitare la registrazione creando un file di configurazione con il contenuto illustrato nella sezione successiva. In alternativa, impostare proton.trace.level=ALL e le opzioni di configurazione desiderate per l'implementazione java.util.logging.Handler. Per le classi di implementazione e le relative opzioni, vedere Package java.util.logging nella documentazione di Java 8 SDK.

Per tracciare i frame di trasporto AMQP, impostare la variabile di ambiente PN_TRACE_FRM=1.

File logging.properties di esempio

Il file di configurazione seguente registra l'output del livello TRACE da Proton-J al file 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

Ridurre i log

Un modo per ridurre la registrazione consiste nel ridurre il livello di verbosità. Un altro modo consiste nell'aggiungere filtri che escludono i log dai pacchetti di nomi di logger, ad esempio com.azure.messaging.servicebus o com.azure.core.amqp. Per esempi, vedere i file XML nelle sezioni Configurare Log4J 2 e Configurare logback.

Quando si invia un bug, i messaggi di log delle classi nei pacchetti seguenti sono interessanti:

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • L'eccezione è che è possibile ignorare il messaggio di onDelivery in ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Concorrenza in ServiceBusProcessorClient

ServiceBusProcessorClient consente all'applicazione di configurare il numero di chiamate al gestore messaggi che devono essere eseguite contemporaneamente. Questa configurazione consente di elaborare più messaggi in parallelo. Per un ServiceBusProcessorClient che consuma messaggi da un'entità senza sessione, l'applicazione può configurare il livello di concorrenza desiderato usando l'API maxConcurrentCalls. La concorrenza desiderata per un'entità abilitata per la sessione è pari a maxConcurrentSessions volte maxConcurrentCalls.

Se l'applicazione osserva un numero inferiore di chiamate simultanee al gestore messaggi rispetto alla concorrenza che è stata configurata, potrebbe essere perché il pool di thread non è dimensionato correttamente.

ServiceBusProcessorClient usa thread daemon del pool di thread del globale Reactor boundedElastic per richiamare il gestore dei messaggi. Il numero massimo di thread simultanei in questo pool è limitato da un limite. Per impostazione predefinita, questo limite è dieci volte il numero di core CPU disponibili. Affinché il ServiceBusProcessorClient supporti in modo efficace la concorrenza desiderata dell'applicazione (maxConcurrentCalls o maxConcurrentSessions volte maxConcurrentCalls), è necessario avere un valore limite di pool boundedElastic superiore alla concorrenza desiderata. È possibile eseguire l'override del limite predefinito impostando la proprietà di sistema reactor.schedulers.defaultBoundedElasticSize.

È consigliabile ottimizzare il pool di thread e l'allocazione della CPU caso per caso. Tuttavia, quando si esegue l'override del limite di pool, come punto di partenza, limitare i thread simultanei a circa 20-30 per core CPU. È consigliabile limitare la concorrenza desiderata per ogni istanza di ServiceBusProcessorClient a circa 20-30. Profila e misura il caso d'uso specifico e ottimizza gli aspetti di concorrenza di conseguenza. Per scenari di carico elevato, è consigliabile eseguire più istanze di ServiceBusProcessorClient in cui ogni istanza viene compilata da una nuova istanza di ServiceBusClientBuilder. Prendere in considerazione anche l'esecuzione di ogni ServiceBusProcessorClient in un host dedicato, ad esempio un contenitore o una macchina virtuale, in modo che il tempo di inattività in un host non influisca sull'elaborazione complessiva dei messaggi.

Tenere presente che l'impostazione di un valore elevato per il limite di pool in un host con pochi core CPU avrebbe effetti negativi. Alcuni segni di risorse CPU basse o di un pool con un numero eccessivo di thread in meno CPU sono: timeout frequenti, blocco perso, deadlock o velocità effettiva inferiore. Se si esegue l'applicazione Java in un contenitore, è consigliabile usare due o più core vCPU. Non è consigliabile selezionare meno di 1 core vCPU quando si esegue un'applicazione Java in ambienti containerizzati. Per consigli approfonditi sull'allocazione delle risorse, vedere Containerize your Java applications.

Collo di bottiglia nella condivisione delle connessioni

Tutti i client creati da un'istanza condivisa di ServiceBusClientBuilder condividono la stessa connessione allo spazio dei nomi del Service Bus.

L'uso di una connessione condivisa permette di eseguire operazioni di multiplexing tra i client su una singola connessione, ma la condivisione può anche diventare un collo di bottiglia se ci sono molti client o se i client, insieme, generano un carico elevato. A ogni connessione è associato un thread di I/O. Durante la condivisione della connessione, i client inseriscono i loro compiti nella coda di lavoro del thread di I/O condiviso e il progresso di ciascun client dipende dal completamento rapido e puntuale dei loro compiti nella coda. Il thread di I/O gestisce l'attività accodata in modo seriale. Ovvero, se la coda di lavoro del thread di I/O di una connessione condivisa finisce con un sacco di lavoro in sospeso da gestire, i sintomi sono simili a quelli della CPU bassa. Questa condizione è descritta nella sezione precedente sulla concorrenza - ad esempio, blocco dei client, timeout, perdita del blocco o rallentamento nel percorso di ripristino.

Service Bus SDK usa il modello di denominazione reactor-executor-* per il thread di connessione I/O. Quando l'applicazione riscontra un collo di bottiglia nella connessione condivisa, ciò potrebbe riflettersi nell'utilizzo della CPU dei thread di I/O. Inoltre, nel dump dell'heap o nella memoria dinamica, l'oggetto ReactorDispatcher$workQueue è la coda di lavoro del thread di I/O. Una lunga coda di lavoro nello snapshot di memoria nel periodo di congestione potrebbe indicare che il thread di I/O condiviso è sovraccarico con operazioni in sospeso.

Pertanto, se il carico dell'applicazione su un endpoint del Bus di Servizio è ragionevolmente elevato in termini di numero complessivo di messaggi inviati-ricevuti o dimensioni del payload, è consigliabile utilizzare un'istanza di builder separata per ogni client che si costruisce. Ad esempio, per ogni entità - coda o argomento - è possibile creare un nuovo ServiceBusClientBuilder e costruire un client a partire da esso. In caso di carico estremamente elevato per un'entità specifica, è possibile creare più istanze client per tale entità o eseguire client in più host, ad esempio contenitori o macchine virtuali, per bilanciare il carico.

I client si arrestano quando si usa l'endpoint personalizzato del Gateway applicazioni

L'indirizzo dell'endpoint personalizzato fa riferimento a un indirizzo endpoint HTTPS fornito dall'applicazione, indirizzabile al Service Bus o configurato per instradare il traffico al Service Bus. Il gateway applicativo di Azure semplifica la creazione di un front-end HTTPS che inoltra il traffico al Service Bus. È possibile configurare l'SDK del bus di servizio per un'applicazione in modo da usare un indirizzo IP front-end del gateway applicazione come endpoint personalizzato per connettersi al bus di servizio.

Il gateway applicativo offre diversi criteri di sicurezza che supportano diverse versioni del protocollo TLS. Esistono criteri predefiniti che applicano TLSv1.2 come versione minima, esistono anche criteri precedenti con TLSv1.0 come versione minima. Il front-end HTTPS avrà una politica TLS applicata.

Al momento, l'SDK del Service Bus non riconosce determinate terminazioni TCP remote dal front-end del gateway delle applicazioni, che utilizza TLSv1.0 come versione minima. Ad esempio, se il front-end invia TCP FIN, i pacchetti ACK per chiudere la connessione quando le relative proprietà vengono aggiornate, l'SDK non riesce a rilevarlo, quindi non si riconnette e i client non possono più inviare o ricevere messaggi. Tale arresto si verifica solo quando si usa TLSv1.0 come versione minima. Per attenuare il problema, usare una politica di sicurezza con TLSv1.2 o versione successiva come versione minima per il front-end del gateway applicativo.

Il supporto per TLSv1.0 e 1.1 in tutti i servizi di Azure è già annunciato terminare entro il 31 ottobre 2024, quindi è consigliabile passare a TLSv1.2.

Il blocco del messaggio o della sessione viene perso

Una coda o una sottoscrizione di un argomento del Service Bus ha una durata di blocco impostata a livello di risorsa. Quando il client ricevitore estrae un messaggio dalla risorsa, il broker del bus di servizio applica un blocco iniziale al messaggio. Il blocco iniziale dura per il periodo impostato a livello di risorsa. Se il blocco del messaggio non viene rinnovato prima della scadenza, il broker del bus di servizio rilascia il messaggio per renderlo disponibile per altri ricevitori. Se l'applicazione tenta di completare o abbandonare un messaggio dopo la scadenza del blocco, la chiamata API non riesce con l'errore com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

Il client di Service Bus supporta l'esecuzione di un'attività in background per il rinnovo continuo del blocco del messaggio, eseguita ogni volta prima della scadenza del blocco. Per impostazione predefinita, l'attività di rinnovo del blocco viene eseguita per 5 minuti. È possibile regolare la durata del rinnovo del blocco utilizzando ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Se si passa il valore Duration.ZERO, l'attività di rinnovo del blocco è disabilitata.

Gli elenchi seguenti descrivono alcuni dei modelli di utilizzo o degli ambienti host che possono causare l'errore di blocco perso:

  • L'attività di rinnovo del blocco è disabilitata e il tempo di elaborazione dei messaggi dell'applicazione supera la durata del blocco impostata a livello di risorsa.

  • Il tempo di elaborazione dei messaggi dell'applicazione supera la durata dell'attività di rinnovo del blocco configurata. Si noti che, se la durata del rinnovo del blocco non è impostata in modo esplicito, il valore predefinito è 5 minuti.

  • L'applicazione ha attivato la funzionalità di prelettura impostando il valore di prelettura su un numero intero positivo usando ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Quando la funzionalità di prefetch è abilitata, il client recupererà un numero di messaggi pari al prefetch dall'entità del Service Bus - coda o topic - e li archivierà nel buffer di prefetch in memoria. I messaggi rimangono nel buffer di prelettura fino a quando non vengono ricevuti nell'applicazione. Il client non estende il blocco dei messaggi mentre si trovano nel buffer di prelettura. Se l'elaborazione dell'applicazione richiede così tanto tempo che i blocchi dei messaggi scadono mentre rimangono nel buffer di prelettura, l'applicazione potrebbe acquisire i messaggi con un blocco scaduto. Per ulteriori informazioni, vedere Perché Prefetch non è l'opzione predefinita?

  • L'ambiente host presenta occasionali problemi di rete - ad esempio un guasto temporaneo o un'interruzione della rete - che impediscono al compito di rinnovo di rinnovare il blocco in tempo.

  • L'ambiente host non dispone di CPU sufficienti o presenta carenze di cicli CPU intermittenti che ritardano l'esecuzione tempestiva dell'attività di rinnovo del blocco.

  • L'ora del sistema host non è accurata, ad esempio l'orologio è asimmetrico, ritardando l'attività di rinnovo del blocco e impedendone l'esecuzione in tempo.

  • Il thread di I/O di connessione è sovraccarico, con effetti sulla possibilità di eseguire tempestivamente le chiamate di rinnovo del blocco di rete. I due scenari seguenti possono causare questo problema:

  • Un modello di applicazione comune che aumenta la probabilità di un errore di blocco perso comporta la pianificazione delle attività di rinnovo del blocco a esecuzione prolungata, ad esempio attività con durate di diverse ore. Come accennato in precedenza, vari fattori al di fuori del controllo di un client del bus di servizio possono interferire con il rinnovo corretto del blocco, quindi le progettazioni delle applicazioni devono evitare di assumere un rinnovo garantito in periodi prolungati. Per evitare di dover rielaborare le operazioni a esecuzione prolungata, è consigliabile suddividere il lavoro in blocchi più piccoli o implementare la logica di checkpoint idempotente.

Il numero di attività di rinnovo del blocco nel client è uguale ai valori dei parametri di maxMessages o maxConcurrentCalls impostati per ServiceBusProcessorClient o ServiceBusReceiverClient.receiveMessages. Un numero elevato di attività di rinnovo dei blocchi che effettuano più chiamate di rete può avere un effetto negativo anche nella limitazione dello spazio dei nomi del bus di servizio.

Se l'host non è sufficientemente dotato di risorse, il lock può essere comunque perso anche se sono in corso solo alcune attività di rinnovo del lock. Se si esegue l'applicazione Java in un contenitore, è consigliabile usare due o più core vCPU. Non è consigliabile selezionare un core vCPU inferiore a 1 quando si eseguono applicazioni Java in ambienti in contenitori. Per consigli approfonditi sull'allocazione delle risorse, vedere Containerize your Java applications.

Le stesse osservazioni sui blocchi sono rilevanti anche per una coda del bus di servizio o una sottoscrizione di argomento con sessione abilitata. Quando il client ricevitore si connette a una sessione nella risorsa, il broker applica un blocco iniziale alla sessione. Per mantenere il blocco nella sessione, l'attività di rinnovo del blocco nel client deve continuare a rinnovare il blocco della sessione prima della scadenza. Per una risorsa abilitata per la sessione, le partizioni sottostanti talvolta si spostano per ottenere il bilanciamento del carico tra i nodi del bus di servizio, ad esempio quando vengono aggiunti nuovi nodi per condividere il carico. In questo caso, i blocchi di sessione possono essere persi. Se l'applicazione tenta di completare o abbandonare un messaggio dopo la perdita del blocco della sessione, la chiamata API non riesce con l'errore com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Passaggi successivi

Se le linee guida per la risoluzione dei problemi in questo articolo non consentono di risolvere i problemi quando si usano le librerie client di Azure SDK per Java, è consigliabile segnalare un problema nel repository GitHub Azure SDK per Java.