Édition

Modèle des consommateurs concurrents

Azure Functions
Azure Service Bus

Ce modèle vise à permettre à plusieurs consommateurs concurrents de traiter les messages reçus sur un même canal de messagerie. Avec plusieurs consommateurs simultanés, un système peut traiter simultanément plusieurs messages de façon à optimiser le débit, à améliorer la scalabilité et la disponibilité et à équilibrer la charge de travail.

Contexte et problème

Une application s’exécutant dans le cloud a de nombreuses demandes à gérer. Plutôt que de traiter chaque demande de façon synchrone, une technique courante consiste pour l’application à les passer à un autre service (service consommateur) via un système de messagerie pour être traitées de façon asynchrone. Cette stratégie permet de s’assurer que la logique métier de l’application n’est pas bloquée pendant le traitement des demandes.

Le nombre de demandes peut varier considérablement au fil du temps, et ce pour diverses raisons. Une augmentation soudaine de l’activité utilisateur ou des demandes agrégées en provenance de plusieurs locataires clients peut occasionner une charge de travail imprévisible. Aux heures de pointe, un système peut être amené à traiter plusieurs centaines de demandes à la seconde, alors qu’à d’autres moments, leur nombre peut être très faible. Par ailleurs, la nature du travail effectué pour traiter ces demandes peut varier considérablement. En utilisant une seule instance du service consommateur, vous pouvez faire en sorte que cette instance soit inondée de requêtes. Ou bien, le système de messagerie peut être surchargé par un flux de messages provenant de l’application. Pour gérer cette charge de travail fluctuante, le système peut exécuter plusieurs instances du service consommateur. Cependant, ces consommateurs doivent être coordonnés pour faire en sorte que chaque message ne soit qu’à un seul consommateur. La charge de travail doit aussi faire l’objet d’un équilibrage de charge entre les consommateurs pour empêcher une instance de devenir un goulot d’étranglement.

Solution

Utilisez une file d’attente de messages pour implémenter le canal de communication entre l’application et les instances du service consommateur. L’application poste les demandes sous forme de messages dans la file d’attente, et les instances du service consommateur reçoivent les messages de la file d’attente pour ensuite les traiter. Cette approche permet à un même pool d’instances du service consommateur de traiter les messages de n’importe quelle instance de l’application. La figure illustre l’utilisation d’une file d’attente de messages pour distribuer le travail aux instances d’un service.

Utilisation d’une file d’attente de messages pour distribuer le travail aux instances d’un service

Remarque

Bien qu’il y ait plusieurs consommateurs de ces messages, cela ne correspond pas au modèle de Publication Abonnement (pub/sub) Avec l’approche Des consommateurs concurrents, chaque message est transmis à un seul consommateur pour le traitement, tandis qu’avec l’approche Pub/Sub, tous les consommateurs reçoivent chaque message.

Cette solution offre les avantages suivants :

  • Elle fournit un système à charge nivelée qui peut gérer les écarts importants dans le volume de demandes envoyées par les instances de l’application. La file d’attente joue le rôle de tampon entre les instances de l’application et les instances du service consommateur. Ce tampon peut aider à réduire l’impact sur la disponibilité et la réactivité, à la fois pour l’application et les instances du service. Pour en savoir plus, consultez Modèle de nivellement de la charge basé sur une file d’attente. La gestion d’un message qui demande un traitement de longue durée n’empêche pas la gestion simultanée d’autres messages par d’autres instances du service consommateur.

  • Elle améliore la fiabilité. Si un producteur communique directement avec un consommateur au lieu d’utiliser ce modèle, mais qu’il ne surveille pas le consommateur, il est fort probable que les messages seront perdus ou ne pourront pas être traités en cas de défaillance du consommateur. Dans ce modèle, les messages ne sont pas envoyés à une instance de service spécifique. Une instance de service défaillante ne bloquera pas un producteur, et les messages pourront être traités par n’importe quelle instance de service active.

  • Elle ne nécessite pas de coordination complexe entre les consommateurs ou entre le producteur et les instances de consommateur. La file d’attente de messages offre la garantie que chaque message est remis au moins une fois.

  • Elle est scalable. Quand vous appliquez une mise à l’échelle automatique, le système peut augmenter ou diminuer dynamiquement le nombre d’instances du service consommateur à mesure que le volume de messages évolue.

  • Elle peut améliorer la résilience si la file d’attente de messages fournit des opérations de lecture transactionnelles. Si une instance du service consommateur lit et traite le message dans le cadre d’une opération transactionnelle et que l’instance du service consommateur subit une défaillance, ce modèle peut faire en sorte que le message soit retourné à la file d’attente pour être récupéré et traité par une autre instance du service consommateur. Afin d’atténuer le risque d’échec en continu d’un message, nous vous recommandons d’utiliser des files d’attente de lettres mortes.

Problèmes et considérations

Prenez en compte les points suivants lorsque vous choisissez comment implémenter ce modèle :

  • Classement des messages : l’ordre dans lequel les instances du service consommateur reçoivent les messages n’est pas garanti et ne reflète pas nécessairement l’ordre dans lequel les messages ont été créés. Concevez le système de sorte que le traitement des messages soit idempotent. Vous éliminerez ainsi toute dépendance vis-à-vis de l’ordre de traitement des messages. Pour plus d’informations, consultez Idempotency Patterns dans le blog de Jonathon Oliver.

    Les files d’attente Microsoft Azure Service Bus peuvent implémenter un classement premier entrée premier sorti en utilisant des sessions de messagerie. Pour plus d’informations, consultez Modèles de messagerie utilisant des sessions.

  • Conception de services à des fins de résilience : si le système est conçu pour détecter et redémarrer les instances de service en échec, il peut être nécessaire d’implémenter le traitement effectué par les instances de service en tant qu’opérations idempotent pour limiter les effets d’un message unique qui serait récupéré et traité plusieurs fois.

  • Détection des messages incohérents : un message au format incorrect ou une tâche qui nécessite un accès à des ressources non disponibles peut entraîner l’échec d’une instance de service. Le système doit éviter que ces messages soient retournés à la file d’attente et au lieu de cela capturer et stocker les détails de ces messages à un autre emplacement en vue de leur analyse, si nécessaire.

  • Gestion des résultats : l’instance de service traitant un message est entièrement dissociée de la logique d’application qui génère le message, et il se peut qu’elles ne puissent pas communiquer directement. Si l’instance de service génère des résultats qui doivent être retransmis à la logique d’application, ces informations doivent être stockées à un emplacement accessible aux deux. Pour éviter que la logique d’application récupère des données incomplètes, le système doit indiquer à quel moment le traitement est terminé.

    Si vous utilisez Azure, un processus de travail peut retransmettre les résultats à la logique d’application en utilisant une file d’attente de réponses de messages dédiée. La logique d’application doit pouvoir mettre en corrélation ces résultats avec le message d’origine. Ce scénario est décrit plus en détail dans Notions élémentaires sur la messagerie asynchrone.

  • Mise à l’échelle du système de messagerie : dans une solution à grande échelle, une file d’attente de messages unique peut être submergée par le nombre de messages et devenir un goulot d’étranglement dans le système. Dans ce cas, envisagez de partitionner le système de messagerie pour envoyer les messages de producteurs spécifiques à une file d’attente particulière, ou utilisez l’équilibrage de charge pour répartir les messages entre plusieurs files d’attente de messages.

  • Assurer la fiabilité du système de messagerie : disposer d’un système de messagerie fiable est une nécessité pour garantir qu’un message ne se perd pas après avoir été mis en file d’attente par l’application. Ce système est essentiel pour garantir que tous les messages sont remis au moins une fois.

Quand utiliser ce modèle

Utilisez ce modèle dans les situations suivantes :

  • La charge de travail d’une application est divisée en tâches qui peuvent s’exécuter de façon asynchrone.
  • Les tâches sont indépendantes et peuvent s’exécuter en parallèle.
  • le volume de travail est très variable, ce qui nécessite une solution scalable.
  • La solution doit offrir une haute disponibilité et doit être résiliente en cas d’échec du traitement d’une tâche.

Ce modèle peut ne pas avoir d’utilité dans les cas suivants :

  • Il n’est pas facile de séparer la charge de travail d’application en tâches discrètes ou il existe un niveau de dépendance élevé entre les tâches.
  • Les tâches doivent être effectuées de façon synchrone et la logique d’application doit attendre qu’une tâche se termine avant de continuer.
  • Les tâches doivent être effectuées dans un ordre précis.

Certains systèmes de messagerie prennent en charge les sessions qui permettent à un producteur de regrouper les messages et de veiller à ce qu’ils soient tous gérés par le même consommateur. Ce mécanisme peut être utilisé avec des messages classés par ordre de priorité (s’ils sont pris en charge) pour implémenter une forme de classement de messages qui remet les messages dans l’ordre d’un producteur à un consommateur unique.

Conception de la charge de travail

Un architecte doit évaluer la façon dont le modèle des consommateurs concurrents peut être utilisé dans la conception de leurs charges de travail pour se conformer aux objectifs et principes abordés dans les piliers d’Azure Well-Architected Framework. Par exemple :

Pilier Comment ce modèle soutient les objectifs des piliers.
Les décisions relatives à la fiabilité contribuent à rendre votre charge de travail résiliente aux dysfonctionnements et à s’assurer qu’elle retrouve un état de fonctionnement optimal après une défaillance. Ce modèle crée une redondance dans le traitement de la file d’attente en considérant les consommateurs comme des réplicas, de sorte qu’une défaillance de l’instance n’empêche pas d’autres consommateurs de traiter les messages de la file d’attente.

- RE :05 Redondance
- RE :07 Travaux en arrière-plan
L’optimisation des coûts est axée sur le maintien et l’amélioration du retour sur investissement de votre charge de travail. Ce modèle peut vous aider à optimiser les coûts en permettant une mise à l’échelle basée sur la profondeur de la file d’attente, jusqu’à zéro lorsque cette dernière est vide. Il peut également optimiser les coûts en vous permettant de limiter le nombre maximal d’instances de consommateur simultanées.

- CO :05 Optimisation du taux
- CO :07 Coûts composants
L’efficacité des performances permet à votre charge de travail de répondre efficacement aux demandes grâce à des optimisations de la mise à l’échelle, des données, du code. La répartition de la charge sur tous les nœuds consommateurs augmente l’utilisation et la mise à l’échelle dynamique basée sur la profondeur de la file d’attente minimise le surapprovisionnement.

- PE :05 Mise à l’échelle et partitionnement
- PE :07 Code et infrastructure

Comme pour toute autre décision de conception, il convient de prendre en compte les compromis par rapport aux objectifs des autres piliers qui pourraient être introduits avec ce modèle.

Exemple

Azure fournit des files d’attente Service Bus et des déclencheurs de files d’attente Azure Function qui, lorsqu’ils sont combinés, sont une implémentation directe de ce modèle de conception de Cloud. Azure Functions s’intègre avec Azure Service Bus via des déclencheurs et des liaisons. L’intégration avec Service Bus vous permet de créer des fonctions qui consomment des messages de file d’attente envoyés par des serveurs de publication. La ou les applications de publication publient des messages dans une file d’attente, et les consommateurs, qui sont implémentés en tant que Azure Functions, peuvent récupérer des messages de cette file d’attente et les gérer.

Pour la résilience, une file d’attente Service Bus permet à un consommateur d’utiliser le mode PeekLock lorsqu’il récupère un message de la file d’attente ; ce mode ne supprime pas réellement le message : il ne fait que le masquer aux autres consommateurs. Le runtime d’Azure Functions reçoit un message en mode PeekLock ; si la fonction se termine correctement, il appelle la méthode Complete sur le message, ou il peut appeler la méthode Abandon si la fonction échoue et le message redevient visible, ce qui permet à un autre consommateur de le récupérer. Si la fonction s’exécute au-delà du délai d’attente PeekLock, le verrou est automatiquement renouvelé tant que la fonction s’exécute.

Azure Functions peut augmenter/diminuer la puissance en fonction de la profondeur de la file d’attente, tout en jouant le rôle de consommateurs concurrents de la file d’attente. Si plusieurs instances des fonctions sont créées, elles sont toutes en concurrence en extrayant et traitant les messages de manière indépendante.

Pour obtenir des informations détaillées sur l’utilisation des files d’attente Azure Service Bus, consultez Files d’attente, rubriques et abonnements Service Bus.

Pour plus d’informations sur les files d’attente déclenchées par Azure Functions, consultez Déclencheur Azure Service Bus pour Azure Functions.

Le code suivant vous indique comment créer un message et l’envoyer à une file d’attente Service Bus à l’aide d’une instance ServiceBusClient.

private string serviceBusConnectionString = ...;
...

  public async Task SendMessagesAsync(CancellationToken  ct)
  {
   try
   {
    var msgNumber = 0;

    var serviceBusClient = new ServiceBusClient(serviceBusConnectionString);

    // create the sender
    ServiceBusSender sender = serviceBusClient.CreateSender("myqueue");

    while (!ct.IsCancellationRequested)
    {
     // Create a new message to send to the queue
     string messageBody = $"Message {msgNumber}";
     var message = new ServiceBusMessage(messageBody);

     // Write the body of the message to the console
     this._logger.LogInformation($"Sending message: {messageBody}");

     // Send the message to the queue
     await sender.SendMessageAsync(message);

     this._logger.LogInformation("Message successfully sent.");
     msgNumber++;
    }
   }
   catch (Exception exception)
   {
    this._logger.LogException(exception.Message);
   }
  }

L’exemple de code suivant montre un consommateur, écrit en tant que C# Azure Function, qui lit les métadonnées du message et consigne un message de la file d’attente Service Bus. Notez comment l’attribut ServiceBusTrigger est utilisé pour le lier à une file d’attente Service Bus.

[FunctionName("ProcessQueueMessage")]
public static void Run(
    [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")]
    string myQueueItem,
    Int32 deliveryCount,
    DateTime enqueuedTimeUtc,
    string messageId,
    ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function consumed message: {myQueueItem}");
    log.LogInformation($"EnqueuedTimeUtc={enqueuedTimeUtc}");
    log.LogInformation($"DeliveryCount={deliveryCount}");
    log.LogInformation($"MessageId={messageId}");
}

Étapes suivantes

  • Primer de messagerie asynchrone. les files d’attente de messages sont un mécanisme de communication asynchrone. Si un service consommateur doit envoyer une réponse à une application, il peut être nécessaire d’implémenter une certaine forme de messagerie de réponse. Le document Notions élémentaires sur la messagerie asynchrone fournit des informations sur la façon d’implémenter une messagerie de demande/réponse en utilisant des files d’attente de messages.

  • Mise à l’échelle automatique. il est possible de démarrer et d’arrêter des instances d’un service consommateur, car la longueur de la file d’attente dans laquelle les applications postent les messages est variable. La mise à l’échelle automatique peut contribuer à maintenir le débit pendant les périodes d’intense traitement.

Les modèles et les conseils suivants peuvent présenter un intérêt quand il s’agit d’implémenter ce modèle :

  • Modèle de consolidation des ressources de calcul. il est possible de consolider plusieurs instances d’un service consommateur dans un processus unique pour réduire les coûts et les surcharges de gestion. Le modèle de consolidation des ressources de calcul décrit les avantages et les inconvénients de cette approche.

  • Queue-based Load Leveling pattern (Modèle de nivellement de charge basé sur une file d’attente). l’introduction d’une file d’attente de messages peut doter le système de capacités de résilience, permettant ainsi aux instances du service de gérer des volumes très variables de demandes en provenance d’instances d’application. La file d’attente de messages fait office de tampon, ce qui a pour effet de niveler la charge. Le modèle de nivellement de la charge basé sur une file d’attente décrit plus en détail ce scénario.