Condividi tramite


Trasferimenti, blocchi e finalizzazione dei messaggi

La funzionalità principale di un broker di messaggi come il bus di servizio è quella di accettare i messaggi in una coda o in un argomento e conservali rendendoli disponibili per un successivo recupero. Il termine send viene usato per descrivere il processo di trasferimento di un messaggio al broker di messaggi, mentre receive fa riferimento al processo di recupero di un messaggio dal broker.

Quando un client invia un messaggio, vuole in genere sapere se il messaggio è stato correttamente trasferito e accettato dal broker o se si è verificato qualche tipo di errore. Questo riconoscimento positivo o negativo permette al client e al broker di comprendere lo stato del trasferimento del messaggio. Prende quindi il nome di finalizzazione.

Analogamente, quando un broker trasferisce un messaggio a un client, il broker e il client vogliono sapere se il messaggio è stato elaborato correttamente e può quindi essere rimosso oppure se l'elaborazione o il recapito del messaggio non è riuscito e di conseguenza il messaggio deve essere recapitato di nuovo.

Finalizzazione delle operazioni di invio

Usando qualsiasi client API del bus di servizio supportato, le operazioni di invio nel bus di servizio vengono sempre finalizzate in modo esplicito, ovvero l'operazione API attende l'arrivo del risultato dell'accettazione dal bus di servizio e quindi completa l'operazione di invio.

Se il messaggio viene rifiutato dal bus di servizio, il rifiuto contiene un indicatore di errore e un testo con un valore tracking-id. Il rifiuto include anche informazioni relative al fatto che sia possibile o meno provare a eseguire di nuovo l'operazione con la possibilità di avere un esito positivo. Nel client queste informazioni vengono trasformate in un'eccezione, generata per il chiamante dell'operazione di invio. Se il messaggio viene accettato, l'operazione viene completata automaticamente.

Advanced Messaging Queuing Protocol (AMQP) è l'unico protocollo supportato per i client .NET Standard, Java, JavaScript, Python e Go. Per i client .NET Framework, è possibile usare il protocollo SBMP (Service Bus Messaging Protocol) o AMQP. Quando si usa il protocollo AMQP, i trasferimenti e le finalizzazioni di messaggi vengono inserite in una pipeline e sono asincroni. È consigliabile usare le varianti API del modello di programmazione asincrona.

Il 30 settembre 2026 verranno ritirate le librerie dell'SDK del bus di servizio di Azure WindowsAzure.ServiceBus, Microsoft.Azure.ServiceBus e com.microsoft.azure.servicebus, che non sono conformi alle linee guida di Azure SDK. Verrà terminato anche il supporto del protocollo SBMP, quindi non sarà più possibile usare questo protocollo dopo il 30 settembre 2026. Eseguire la migrazione alle librerie più recenti di Azure SDK, che offrono aggiornamenti critici della sicurezza e funzionalità migliorate, prima di tale data.

Anche se le librerie precedenti possono ancora essere usate oltre il 30 settembre 2026, non riceveranno più il supporto e gli aggiornamenti ufficiali da Microsoft. Per altre informazioni, vedere l'annuncio del ritiro del supporto.

Un mittente può inviare diversi messaggi in transito in rapida successione senza dover attendere il riconoscimento di ogni messaggio, come avverrebbe invece con il protocollo SBMP o HTTP 1.1. Le operazioni di invio asincrone vengono completate man mano che i relativi messaggi vengono accettati e archiviati, nelle entità partizionate o quando le operazioni di invio a entità diverse si sovrappongono. Il completamento può avvenire anche fuori dall'ordine di invio originale.

La strategia per gestire il risultato delle operazioni di invio può avere un impatto immediato e significativo sulle prestazioni dell'applicazione. Gli esempi riportati in questa sezione sono scritti in C# e si applicano ai future Java, ai mono Java, alle promesse JavaScript e a concetti equivalenti in altri linguaggi.

Se l'applicazione genera picchi di messaggi, illustrati qui con un ciclo semplice, e si attende il completamento di ogni operazione di invio prima di inviare il messaggio successivo, le API sincrone o asincrone hanno un funzionamento simile e l'invio di 10 messaggi viene completato solo dopo 10 round trip completi sequenziali per la finalizzazione.

Presupponendo una latenza del round trip TCP (Transmission Control Protocol) di 70 millisecondi dovuta alla distanza tra un sito locale e il bus di servizio e consentendo solo 10 ms al bus di servizio per accettare e archiviare ogni messaggio, il ciclo seguente richiede almeno 8 secondi, senza contare il tempo di trasferimento del payload o i potenziali effetti di congestione della route:

for (int i = 0; i < 10; i++)
{
    // creating the message omitted for brevity
    await sender.SendMessageAsync(message);
}

Se l'applicazione avvia le 10 operazioni di invio asincrone in successione immediata e attende il rispettivo completamento separatamente, il tempo di round trip per tali 10 operazioni di invio si sovrappone. I 10 messaggi vengono trasferiti in successione immediata, potenzialmente anche condividendo frame TCP, e la durata complessiva del trasferimento dipende in gran parte dal tempo di rete necessario per trasferire i messaggi al broker.

Basandosi sugli stessi presupposti illustrati per il ciclo precedente, il tempo di esecuzione totale sovrapposto per il ciclo seguente potrebbe rimanere ben sotto un secondo:

var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    tasks.Add(sender.SendMessageAsync(message));
}
await Task.WhenAll(tasks);

È importante notare che tutti i modelli di programmazione asincroni usano una forma di coda di lavoro nascosta basata sulla memoria che contiene le operazioni in sospeso. Se viene restituita l’API di invio, l'attività di invio viene inserita nella coda di lavoro, ma l'azione del protocollo viene avviata solo quando è il turno dell'attività di essere eseguita. Per il codice che tende a eseguire il push di picchi di messaggi e per i casi in cui l'affidabilità è importante, è necessario fare attenzione che non ci siano troppi messaggi "in corso" contemporaneamente, in quanto tutti i messaggi inviati occupano memoria fino a quando non sono in transito.

I semafori, come illustrato nel frammento di codice seguente in C#, sono oggetti di sincronizzazione che consentono, quando necessario, tale limitazione a livello di applicazione. Questo uso di un semaforo consente la presenza al massimo di 10 messaggi in corso contemporaneamente. Uno dei 10 blocchi tramite semaforo disponibili viene acquisito prima dell'invio e rilasciato al completamento dell'invio. L'undicesimo passaggio nel ciclo resta in attesa fino a quando viene completata una delle operazioni di invio precedenti e quindi rende disponibile il blocco:

var semaphore = new SemaphoreSlim(10);

var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    await semaphore.WaitAsync();

    tasks.Add(sender.SendMessageAsync(message).ContinueWith((t)=>semaphore.Release()));
}
await Task.WhenAll(tasks);

Le applicazioni non devono mai avviare un'operazione di invio asincrona adottando l'approccio "fire-and-forget", ovvero senza recuperare il risultato dell'operazione. Ciò potrebbe caricare la coda di attività interna e invisibile fino a esaurire la memoria, impedendo all'applicazione di rilevare gli errori di invio:

for (int i = 0; i < 10; i++)
{
    sender.SendMessageAsync(message); // DON’T DO THIS
}

Con un client AMQP di basso livello, il bus di servizio accetta anche i trasferimenti "pre-finalizzati". Un trasferimento pre-finalizzato è un'operazione di tipo fire-and-forget per la quale il risultato, qualunque esso sia, non viene segnalato al client e il messaggio viene considerato finalizzato quando inviato. La mancanza di feedback per il client significa anche che non ci sono dati disponibili per la diagnostica, quindi questa modalità non è idonea per richiedere assistenza al supporto tecnico di Azure.

Finalizzazione delle operazioni di ricezione

Per le operazioni di ricezione, i client API del bus di servizio consentono due diverse modalità esplicite: ricezione ed eliminazione e blocco di visualizzazione.

ReceiveAndDelete

La modalità di ricezione ed eliminazione indica al broker di considerare tutti i messaggi inviati al client ricevente come finalizzati quando inviati. Questo significa che il messaggio viene considerato usato non appena il broker lo invia in transito. Se il trasferimento del messaggio non riesce, il messaggio viene perso.

Il vantaggio di questa modalità è il fatto che il ricevitore non deve eseguire altre azioni sul messaggio e non viene rallentato dall'attesa del risultato della finalizzazione. Se i dati contenuti nei singoli messaggi hanno un valore basso e/o sono significativi solo per un tempo molto breve, questa modalità è una scelta ragionevole.

PeekLock

La modalità blocco di visualizzazione indica al broker che il client ricevente richiede la finalizzazione esplicita dei messaggi ricevuti. Il messaggio viene reso disponibile per l'elaborazione da parte del ricevitore, mentre viene applicato un blocco esclusivo nel servizio in modo che altri ricevitori concorrenti non possano visualizzarlo. La durata del blocco viene definita inizialmente a livello di coda o di sottoscrizione e può essere estesa dal client proprietario del blocco, tramite l'operazione RenewMessageLockAsync. Per informazioni dettagliate sul rinnovo dei blocchi, vedere la sezione Rinnovare i blocchi in questo articolo.

Quando un messaggio è bloccato, gli altri client che ricevono dalla stessa coda o sottoscrizione possono accettare i blocchi e recuperare i successivi messaggi disponibili non attivamente bloccati. Quando il blocco su un messaggio viene rilasciato in modo esplicito o scade, il messaggio viene posizionato all'inizio o in prossimità dell'inizio dell'ordine di recupero per essere recapitato nuovamente.

Quando il messaggio viene rilasciato ripetutamente dai ricevitori o i ricevitori lasciano scadere il blocco per un numero definito di volte (Max Delivery Count), il messaggio viene rimosso automaticamente dalla coda o dalla sottoscrizione e inserito nella coda di messaggi non recapitabili associata.

Il client ricevente avvia la finalizzazione di un messaggio ricevuto con un riconoscimento positivo quando chiama l’API Complete per il messaggio. Questo indica al broker che il messaggio è stato elaborato correttamente e viene rimosso dalla coda o dalla sottoscrizione. Il broker risponde all'intenzione di finalizzazione del ricevitore indicando se la finalizzazione è stata possibile.

Quando il client ricevente non riesce a elaborare un messaggio, ma vuole che il messaggio venga recapitato nuovamente, può chiedere esplicitamente che il messaggio venga rilasciato e sbloccato subito chiamando l’API Abandon per il messaggio oppure può non fare niente e lasciare che il blocco scada.

Se un client ricevente non riesce a elaborare un messaggio e sa che un nuovo tentativo di eseguire l'operazione e recapitare il messaggio non sarà di aiuto, può rifiutare il messaggio, che viene quindi spostato nella coda di messaggi non recapitabili, chiamando l’API DeadLetter sul messaggio, che consente anche di impostare una proprietà personalizzata, incluso un codice motivo che può essere recuperato con il messaggio dalla coda di messaggi non recapitabili.

Note

Esiste una coda secondaria dei messaggi non recapitabili per una coda o una sottoscrizione a un argomento solo quando è abilitata la funzionalità dei messaggi non recapitabili per la coda o la sottoscrizione.

Un caso speciale di finalizzazione è il differimento. Per informazioni dettagliate, vedere Differimento di messaggi.

Le operazioni Complete, DeadLetter o RenewLock potrebbero non andare a buon fine a causa di problemi di rete, se il blocco applicato è scaduto o se esistono altre condizioni sul lato servizio che impediscono la finalizzazione. In uno degli ultimi casi, il servizio invia un riconoscimento negativo (NAK) che genera un'eccezione nei client API. Se il motivo è un'interruzione della connessione di rete, il blocco viene eliminato perché il bus di servizio non supporta il ripristino dei collegamenti AMQP esistenti in una connessione diversa.

Se il metodo Complete ha esito negativo, cosa che accade in genere nelle fasi finali di gestione dei messaggi e in alcuni casi dopo alcuni minuti di lavoro di elaborazione, l'applicazione ricevente può decidere se mantenere lo stato del lavoro e ignorare lo stesso messaggio quando viene recapitato una seconda volta oppure se scartare il risultato del lavoro e riprovare quando il messaggio viene recapitato nuovamente.

Il meccanismo tipico per identificare le consegne di messaggi duplicati consiste nel controllare , message-idche può e deve essere impostato dal mittente su un valore univoco, possibilmente allineato a un identificatore del processo di origine. Probabilmente, un pianificatore di processi imposta il valore message-id sull'identificatore del processo che sta cercando di assegnare a un ruolo di lavoro con il ruolo di lavoro specificato e il ruolo di lavoro ignora la seconda occorrenza dell'assegnazione del processo, se tale processo è già stato eseguito.

Importante

È importante notare che il blocco che PeekLock o SessionLock acquisisce nel messaggio è volatile e può essere perso nelle condizioni seguenti

  • Aggiornamento del servizio
  • Aggiornamento del sistema operativo
  • Modifica delle proprietà dell'entità (Coda, Argomento, Sottoscrizione) mantenendo il blocco.
  • Se l'applicazione client del bus di servizio perde la connessione al bus di servizio per qualsiasi motivo.
  • Quando si usano sessioni e SessionIdleTimeout è più breve della durata del blocco del messaggio e non viene eseguita alcuna operazione sul messaggio entro il periodo sessionIdletimeout, la sessione scadrà e il blocco del messaggio andrà perso.

Quando il blocco viene perso, il bus di servizio di Azure genera un'eccezione MessageLockLostException o SessionLockLostException, che viene visualizzata nell'applicazione client. In questo caso, la logica di ripetizione dei tentativi predefinita del client deve essere avviata automaticamente e ripetere l'operazione. Inoltre, il numero di recapito del messaggio non viene incrementato.

Rinnovare i blocchi

Il valore predefinito per la durata del blocco è 1 minuto. È possibile specificare un valore diverso per la durata del blocco a livello di coda o sottoscrizione . Il client proprietario del blocco può rinnovare il blocco del messaggio usando i metodi sull'oggetto ricevitore. È invece possibile usare la funzione di rinnovo automatico del blocco, che consente di specificare la durata per la quale si desidera continuare a rinnovare il blocco.

È preferibile impostare la durata del blocco su un valore superiore al tempo di elaborazione normale, in modo da non dover rinnovare il blocco. Il valore massimo è di 5 minuti, pertanto è necessario rinnovare il blocco se si desidera prolungarne la durata. Impostare una durata del blocco superiore al necessario presenta anche alcune implicazioni. Quando il client smette di funzionare, ad esempio, il messaggio tornerà nuovamente disponibile solo al termine del periodo di blocco.

Passaggi successivi