Transferts, verrouillages et règlement des messages

La fonctionnalité centrale d’un répartiteur de messages tel que Service Bus est d’accepter les messages envoyés dans une file d’attente ou une rubrique et de garantir leur disponibilité jusqu’à leur récupération ultérieure. Envoi est le terme couramment utilisé pour désigner le transfert d’un message vers le répartiteur de messages. Réception est le terme couramment utilisé pour désigner le transfert d’un message vers un client destinataire.

Quand un client envoie un message, il souhaite généralement savoir si le message est correctement transféré au répartiteur et accepté par celui-ci ou, au contraire, si une erreur s’est produite pendant l’opération. Cet accusé de réception positif ou négatif permet au client et au répartiteur de connaître l’état du transfert du message. Par conséquent, il est désigné sous le terme de règlement.

De même, quand le répartiteur transfère un message à un client, le répartiteur et le client ont besoin de savoir si le message est bien traité pour pouvoir ensuite être supprimé, ou s’il n’a pas pu être remis ou si le traitement a échoué et le message doit donc être remis une nouvelle fois.

Règlement des opérations d’envoi

Sur tous les clients d’API Service Bus pris en charge, les opérations d’envoi dans Service Bus sont toujours réglées de façon explicite, c’est-à-dire que l’opération d’API attend de recevoir la confirmation de l’acceptation de Service Bus pour terminer l’opération d’envoi.

Si le message est rejeté par Service Bus, le refus contient un indicateur et un texte d’erreur accompagnés d’un ID de suivi. Le rejet inclut également des informations indiquant si une nouvelle tentative de l’opération a des chances de réussir. Sur le client, ces informations sont fournies sous la forme d’une exception, qui est déclenchée à l’attention de l’appelant de l’opération d’envoi. Si le message est accepté, l’opération se termine en mode silencieux.

Le protocole AMQP (Advanced Messaging Queuing Protocol) est le seul protocole pris en charge pour les clients .NET Standard, Java, JavaScript, Python et Go. Pour les clients .NET Framework, vous pouvez utiliser le protocole SBMP (Service Bus Messaging Protocol) ou AMQP. Lorsque vous utilisez le protocole AMQP, les transferts de messages et les règlements sont mis en pipeline et sont exécutés de façon asynchrone. Nous vous recommandons d’utiliser les variantes d’API du modèle de programmation asynchrone.

Le 30 septembre 2026, nous retirerons les bibliothèques WindowsAzure.ServiceBus, Microsoft.Azure.ServiceBus et com.microsoft.azure.servicebus du kit de développement logiciel (SDK) Azure Service Bus, qui ne sont pas conformes aux directives du kit de développement logiciel (SDK) Azure. Nous mettrons également fin à la prise en charge du protocole SBMP. Vous ne pourrez donc plus utiliser ce protocole après le 30 septembre 2026. Migrez vers les dernières bibliothèques du kit de développement logiciel (SDK) Azure, qui offre des correctifs de sécurité critiques et des fonctionnalités améliorées, avant cette date.

Bien que les anciennes bibliothèques puissent toujours être utilisées au-delà du 30 septembre 2026, elles ne seront plus prises en charge officiellement et mises à jour par Microsoft. Pour plus d’informations, consultez l’annonce concernant l’arrêt de la prise en charge.

Cela permet à l’expéditeur de transmettre rapidement plusieurs messages à la suite sur le réseau sans avoir à attendre que chaque message soit accepté, comme cela est le cas avec le protocole SBMP ou HTTP 1.1. Ces opérations d’envoi asynchrones se terminent quand les messages respectifs sont acceptés et stockés sur les entités partitionnées ou en cas de chevauchement de l’opération d’envoi sur différentes entités. Les opérations d’envoi peuvent également se terminer indépendamment de l’ordre d’envoi initial.

La stratégie choisie pour traiter le résultat des opérations d’envoi peut immédiatement et fortement impacter les performances de votre application. Les exemples de cette section sont écrits en C# et s’appliquent aux futures Java, aux monos Java, aux promesses JavaScript et aux concepts équivalents dans d’autres langages.

Si une application générant des rafales de messages, comme illustré ici avec une boucle simple, devait attendre la fin de chaque opération d’envoi avant d’envoyer le message suivant, pour les API synchrones comme pour les API asynchrones, l’envoi de dix messages prendrait dix allers-retours séquentiels complets pour le règlement.

En supposant une distance de latence aller-retour du protocole TCP (Transmission Control Protocol) de 70 millisecondes entre un site local et Service Bus, en donnant seulement un délai de 10 ms à Service Bus pour accepter et stocker chaque message, l’exécution de la boucle suivante prend au moins 8 secondes, sans prendre en compte le temps de transfert de la charge utile ou les effets d’une surcharge du routage potentielle :

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

Si l’application démarre les 10 opérations d’envoi asynchrones à la suite et attend que chacune d’elles soit terminée, le délai des allers-retours pour ces 10 opérations se chevauche. Les dix messages sont immédiatement transférés l’un après l’autre, potentiellement en partageant les trames TCP. La durée totale du transfert dépend en grande partie du temps réseau nécessaire pour envoyer les messages au répartiteur.

En reprenant les mêmes hypothèses que pour la boucle précédente, la durée d’exécution totale avec chevauchement pour la boucle suivante devrait largement rester sous le seuil d’une seconde :

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

Il est important de savoir que tous les modèles de programmation asynchrones utilisent une forme de file d’attente de travail masquée et basée sur la mémoire, qui contient les opérations en attente. Lorsque l’API envoyée retourne des résultats, la tâche d’envoi est mise dans cette file d’attente de travail, mais le protocole commence son action uniquement quand c’est au tour de cette tâche d’être exécutée. Si vous utilisez du code qui envoie (push) souvent des rafales de messages et pour lequel la fiabilité est un critère important, évitez de lancer l’envoi d’un trop grand nombre de messages à la fois, car tous les messages envoyés restent en mémoire jusqu’à ce qu’ils soient mis sur le réseau.

Les sémaphores, comme ceux contenus dans l’extrait de code C# suivant, sont des objets de synchronisation qui permettent d’imposer une limitation au niveau de l’application si nécessaire. Cette utilisation de sémaphore permet l’envoi simultané de dix messages au maximum. L’un des dix verrous de sémaphore disponibles est pris avant l’envoi et est libéré seulement à la fin de l’envoi. Au 11e passage, la boucle attend qu’au moins la fin de l’une des précédentes opérations d’envois, puis libère son verrou :

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);

Les applications ne doivent jamais démarrer une opération d’envoi asynchrone en mode « Fire and Forget » sans récupérer le résultat de l’opération. En effet, cela risque d’entraîner le chargement de la file d’attente de travail interne et masquée jusqu’à épuisement de la mémoire et d’empêcher l’application de détecter les erreurs d’envoi :

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

Avec un client AMQP de bas niveau, Service Bus accepte également les transferts « préréglés ». Un transfert préréglé est une opération en mode « fire and forget » dont le résultat n’est jamais retourné au client et pour laquelle le message est considéré comme réglé dès l’envoi. Le fait qu’aucun résultat ne soit retourné au client implique aussi qu’il n’y a pas de données actionnables disponibles pour les diagnostics, ce qui signifie que ce mode n’est pas éligible à l’aide par le support Azure.

Règlement des opérations de réception

Pour les opérations de réception, les clients d’API Service Bus utilisent deux modes explicites : Receive-and-Delete et Peek-Lock.

ReceiveAndDelete

Le mode Receive-and-Delete indique au répartiteur qu’il doit considérer tous les messages qu’il envoie au client destinataire comme réglés au moment de l’envoi. Cela signifie qu’un message est considéré comme consommé dès que le répartiteur le met sur le réseau. Si le transfert du message échoue, le message est perdu.

L’avantage de ce mode est que le destinataire n’a aucune autre action à effectuer sur le message et qu’il ne perd pas de temps à attendre le résultat du règlement. Si les données contenues dans les messages ont une faible valeur et/ou restent pertinentes pendant une durée très courte, ce mode est un bon choix.

PeekLock

Le mode Peek-Lock indique au répartiteur que le client destinataire souhaite régler explicitement les messages reçus. Le message est mis à la disposition du destinataire en vue de son traitement, mais il est verrouillé en mode exclusif dans le service pour qu’il ne soit pas visible par d’autres destinataires concurrents. La durée du verrou est initialement définie au niveau de la file d’attente ou de l’abonnement, mais elle peut être étendue par le client propriétaire du verrou à l’aide d’une opération RenewMessageLockAsync. Pour plus d’informations sur le renouvellement des verrous, consultez la section Renouveler les verrous dans cet article.

Quand un message est verrouillé, les autres clients qui reçoivent des messages de la même file d’attente ou du même abonnement peuvent acquérir des verrous et récupérer les messages disponibles suivants qui ne sont pas verrouillés. Lorsque le verrou d’un message est explicitement libéré ou qu’il expire, le message est placé jusqu’à la première position dans l’ordre de récupération pour être remis une nouvelle fois.

Si le message est libéré à plusieurs reprises par les récepteurs ou qu’ils laissent le verrou s’écouler un nombre défini de fois (Nombre maximal de livraisons), le message est automatiquement supprimé de la file d’attente ou de l’abonnement et il est mis dans la file d’attente de lettres mortes associée.

Le client destinataire démarre le règlement d’un message reçu avec un accusé de réception positif lorsqu’il appelle l’API Terminer pour le message. Cette opération indique au répartiteur que le message a été traité correctement et que le message est supprimé de la file d’attente ou de l’abonnement. Le répartiteur envoie une réponse à l’intention de règlement du destinataire, dans laquelle il indique si le règlement peut être effectué.

Lorsque le client destinataire ne parvient pas à traiter un message, mais souhaite que le message soit renvoyé, il peut demander explicitement la libération et le déverrouillage instantané du message en appelant l’API Abandonner pour le message ou bien il peut ne rien faire et doit attendre la fin du verrouillage.

Si un client destinataire n’arrive pas à traiter un message et qu’il sait que le résultat sera le même après un deuxième envoi du message et une nouvelle tentative de l’opération, il peut choisir de rejeter le message.Cette action entraîne le déplacement du message vers la file d’attente de lettres mortes, avec l’appel de l’API Lettre morte sur le message,qui permet également de définir une propriété personnalisée contenant un code de raison qui peut être récupéré avec le message à partir de la file d’attente de lettres mortes.

Remarque

Une sous-file d’attente de lettres mortes existe pour une file d’attente ou un abonnement à une rubrique uniquement lorsque la fonctionnalité de lettres mortes est activée pour la file d’attente ou l’abonnement.

Le report est un cas particulier de règlement et fait l’objet d’un article séparé.

Les opérations Complete, DeadLetter ou RenewLock peuvent échouer en raison de problèmes réseau, si le verrou a expiré ou si d’autres conditions côté service empêchent le règlement. Dans l’un de ces cas, le service envoie un accusé de réception négatif qui est exposé sous forme d’exception dans les clients d’API. Si la raison est l’interruption d’une connexion réseau, le verrou est supprimé, car Service Bus ne prend pas en charge la récupération des liens AMQP existants sur une autre connexion.

Si Complete échoue, ce qui arrive généralement à la fin du traitement d’un message et, dans certains cas, quelques minutes après le début du travail de traitement, l’application destinataire peut choisir de garder l’état du travail et d’ignorer le même message lorsqu’il est de nouveau envoyé, de ne pas conserver le résultat du travail et de recommencer l’opération après un nouvel envoi du message.

Le mécanisme standard permettant d’identifier les remises de messages dupliqués consiste à vérifier l’ID du message (message-id). Cet identificateur doit être défini par l’expéditeur à une valeur unique, éventuellement en lien avec un identificateur du processus initial. Un planificateur de travaux préfère généralement définir l’ID du message sur l’identificateur du travail qu’il essaie d’assigner à un processus Worker avec le processus Worker donné. Ainsi, le processus Worker ignore la deuxième occurrence de l’assignation du travail si ce travail est déjà fait.

Important

Il est important de noter que le verrou acquis par PeekLock ou SessionLock sur le message est volatil et peut être perdu dans les conditions suivantes :

  • Mise à jour du service
  • Mise à jour du système d’exploitation
  • Changement des propriétés de l’entité (file d’attente, rubrique, abonnement) pendant le maintien du verrou

Quand le verrou est perdu, Azure Service Bus génère une exception MessageLockLostException ou SessionLockLostException, qui apparaît dans l'application client. Dans ce cas, la logique de nouvelle tentative par défaut du client doit automatiquement démarrer et recommencer l’opération.

Renouveler les verrous

La valeur par défaut de la durée de verrouillage est de 1 minute. Vous pouvez spécifier une valeur différente pour la durée du verrouillage au niveau de la file d’attente ou de l’abonnement. Le client propriétaire du verrou peut renouveler le verrou de message en utilisant des méthodes sur l’objet du récepteur. Au lieu de cela, vous pouvez utiliser la fonctionnalité de renouvellement automatique de verrouillage, dans laquelle vous pouvez spécifier la durée pendant laquelle vous souhaitez que le verrou soit renouvelé.

Il est préférable de définir la durée du verrouillage sur une durée supérieure à votre durée de traitement normale, afin de ne pas avoir à renouveler le verrouillage. La valeur maximale étant de 5 minutes, vous devez renouveler le verrouillage si vous souhaitez prolonger sa durée. Le fait d’avoir une durée de verrouillage plus longue que nécessaire a également des implications. Par exemple, si votre client cesse de fonctionner, le message ne redevient disponible qu’une fois le verrouillage écoulé.

Étapes suivantes