modèle de travail Queue-Centric (Création d’applications cloud Real-World avec Azure)

par Rick Anderson, Tom Dykstra

Télécharger le projet de correction ou télécharger le livre électronique

Le livre électronique Building Real World Cloud Apps avec Azure est basé sur une présentation développée par Scott Guthrie. Il explique 13 modèles et pratiques qui peuvent vous aider à développer avec succès des applications web pour le cloud. Pour plus d’informations sur le livre électronique, consultez le premier chapitre.

Plus tôt, nous avons vu que l’utilisation de plusieurs services peut entraîner un contrat SLA « composite », où le contrat SLA efficace de l’application est le produit des contrats SLA individuels. Par exemple, l’application Corriger utilise sites web, stockage et SQL Database. Si l’un de ces services échoue, l’application renvoie une erreur à l’utilisateur.

La mise en cache est un bon moyen de gérer les échecs temporaires pour le contenu en lecture seule. Mais que se passe-t-il si votre application doit fonctionner ? Par exemple, lorsque l’utilisateur envoie une nouvelle tâche De correction, l’application ne peut pas simplement placer la tâche dans le cache. L’application doit écrire la tâche Corriger dans un magasin de données persistant afin qu’elle puisse être traitée.

C’est là qu’intervient le modèle de travail centré sur la file d’attente. Ce modèle permet un couplage libre entre une couche web et un service back-end.

Voici comment fonctionne le modèle. Lorsque l’application obtient une demande, elle place un élément de travail dans une file d’attente et retourne immédiatement la réponse. Ensuite, un processus back-end distinct extrait les éléments de travail de la file d’attente et effectue le travail.

Le modèle de travail centré sur la file d’attente est utile pour :

  • Travail chronophage (latence élevée).
  • Travail qui nécessite un service externe qui n’est pas toujours disponible.
  • Travail gourmand en ressources (processeur élevé).
  • Travail qui bénéficierait du nivellement du débit (sous réserve de rafales de charge soudaines).

Latence réduite

Les files d’attente sont utiles chaque fois que vous effectuez un travail fastidieux. Si une tâche prend quelques secondes ou plus, au lieu de bloquer l’utilisateur final, placez l’élément de travail dans une file d’attente. Indiquez à l’utilisateur « Nous travaillons dessus », puis utilisez un écouteur de file d’attente pour traiter la tâche en arrière-plan.

Par exemple, lorsque vous achetez quelque chose chez un détaillant en ligne, le site web confirme immédiatement votre commande. Mais cela ne signifie pas que vos affaires sont déjà dans un camion en cours de livraison. Ils placent une tâche dans une file d’attente et, en arrière-plan, ils font le crédit case activée, préparent vos articles pour l’expédition, etc.

Pour les scénarios avec une latence courte, le temps total de bout en bout peut être plus long à l’aide d’une file d’attente, par rapport à l’exécution synchrone de la tâche. Mais même dans ce cas, les autres avantages peuvent l’emporter sur cet inconvénient.

Fiabilité accrue

Dans la version de Fix It que nous avons examiné jusqu’à présent, le serveur frontal web est étroitement associé au SQL Database back-end. Si le service de base de données SQL n’est pas disponible, l’utilisateur obtient une erreur. Si les nouvelles tentatives ne fonctionnent pas (autrement dit, l’échec est plus que temporaire), la seule chose que vous pouvez faire est d’afficher une erreur et de demander à l’utilisateur de réessayer ultérieurement.

Diagramme montrant l’échec du front-end web en cas d’échec de SQL Database back-end

À l’aide de files d’attente, lorsqu’un utilisateur envoie une tâche de correction, l’application écrit un message dans la file d’attente. La charge utile du message est une représentation JSON de la tâche. Dès que le message est écrit dans la file d’attente, l’application retourne et affiche immédiatement un message de réussite à l’utilisateur.

Si l’un des services back-end( par exemple la base de données SQL ou l’écouteur de file d’attente) passe hors connexion, les utilisateurs peuvent toujours soumettre de nouvelles tâches de correction. Les messages seront simplement mis en file d’attente jusqu’à ce que les services principaux soient à nouveau disponibles. À ce stade, les services back-end se rattrapent sur le backlog.

Diagramme montrant le serveur web frontal continuant à fonctionner en cas d’erreur SQL Database

De plus, vous pouvez désormais ajouter une logique back-end supplémentaire sans vous soucier de la résilience du front-end. Par exemple, vous pouvez envoyer un e-mail ou un SMS au propriétaire chaque fois qu’un nouveau correctif est attribué. Si le service e-mail ou SMS devient indisponible, vous pouvez traiter tout le reste, puis placer un message dans une file d’attente distincte pour l’envoi d’e-mails/SMS.

Auparavant, notre contrat SLA effectif était Web Apps × stockage × SQL Database = 99,7 %. (Voir Conception pour survivre aux échecs.)

Lorsque nous modifions l’application pour utiliser une file d’attente, le front-end web dépend uniquement de Web Apps et de stockage, pour un contrat SLA composite de 99,8 %. (Notez que les files d’attente font partie du service de stockage Azure. Elles sont donc incluses dans le même contrat SLA que le stockage d’objets blob.)

Si vous avez besoin de 99,8 %, vous pouvez créer deux files d’attente dans deux régions différentes. Désignez l’un comme principal et l’autre comme secondaire. Dans votre application, basculez vers la file d’attente secondaire si la file d’attente principale n’est pas disponible. Le risque que les deux ne soient pas disponibles en même temps est très faible.

Nivellement des taux et mise à l’échelle indépendante

Les files d’attente sont également utiles pour ce que l’on appelle le nivellement de débit ou le nivellement de charge.

Les applications web sont souvent sensibles aux rafales soudaines du trafic. Bien que vous puissiez utiliser la mise à l’échelle automatique pour ajouter automatiquement des serveurs web afin de gérer l’augmentation du trafic web, la mise à l’échelle automatique peut ne pas être en mesure de réagir assez rapidement pour gérer les pics de charge brusques. Si les serveurs web peuvent décharger une partie du travail qu’ils doivent effectuer en écrivant un message dans une file d’attente, ils peuvent gérer davantage de trafic. Un service back-end peut ensuite lire les messages de la file d’attente et les traiter. La profondeur de la file d’attente augmente ou diminue à mesure que la charge entrante varie.

Avec une grande partie de son travail fastidieux non chargé dans un service back-end, la couche Web peut plus facilement répondre aux pics soudains de trafic. Et vous économisez de l’argent, car une quantité donnée de trafic peut être gérée par moins de serveurs web.

Vous pouvez mettre à l’échelle la couche Web et le service back-end indépendamment. Par exemple, vous pouvez avoir besoin de trois serveurs web, mais d’un seul serveur qui traite les messages de file d’attente. Ou si vous exécutez une tâche nécessitant beaucoup de ressources de calcul en arrière-plan, vous aurez peut-être besoin de serveurs principaux supplémentaires.

Diagramme montrant une représentation des niveaux d’échelle pendant qu’ils traitent les tâches dans la file d’attente

La mise à l’échelle automatique fonctionne avec les services principaux ainsi qu’avec la couche Web. Vous pouvez effectuer un scale-up ou un scale-down du nombre de machines virtuelles qui traitent les tâches dans la file d’attente, en fonction de l’utilisation du processeur des machines virtuelles back-end. Vous pouvez également mettre à l’échelle automatiquement en fonction du nombre d’éléments dans une file d’attente. Par exemple, vous pouvez indiquer à la mise à l’échelle automatique d’essayer de ne pas conserver plus de 10 éléments dans la file d’attente. Si la file d’attente comporte plus de 10 éléments, la mise à l’échelle automatique ajoute des machines virtuelles. Lorsqu’ils rattrapent leur retard, la mise à l’échelle automatique détruit les machines virtuelles supplémentaires.

Ajout de files d’attente à l’application Fix It

Pour implémenter le modèle de file d’attente, nous devons apporter deux modifications à l’application Corriger.

  • Lorsqu’un utilisateur envoie une nouvelle tâche Corriger, placez la tâche dans la file d’attente, au lieu de l’écrire dans la base de données.
  • Créez un service principal qui traite les messages dans la file d’attente.

Pour la file d’attente, nous allons utiliser le service Stockage File d’attente Azure. Une autre option consiste à utiliser Azure Service Bus.

Pour déterminer le service de file d’attente à utiliser, réfléchissez à la façon dont votre application doit envoyer et recevoir les messages dans la file d’attente :

  • Si vous avez des producteurs et des consommateurs concurrents qui coopèrent, envisagez d’utiliser le service Stockage File d’attente Azure. « Producteurs coopérants » signifie que plusieurs processus ajoutent des messages à une file d’attente. « Consommateurs concurrents » signifie que plusieurs processus extrayent les messages de la file d’attente pour les traiter, mais un message donné ne peut être traité que par un seul « consommateur ». Si vous avez besoin d’un débit supérieur à celui que vous pouvez obtenir avec une file d’attente unique, utilisez des files d’attente supplémentaires et/ou des comptes de stockage supplémentaires.
  • Si vous avez besoin d’un modèle de publication/abonnement, envisagez d’utiliser Azure Service Bus Files d’attente.

L’application Fix It correspond au modèle des producteurs et des consommateurs concurrents qui coopèrent.

Une autre considération est la disponibilité des applications. Le service de stockage file d’attente fait partie du même service que celui que nous utilisons pour le stockage d’objets blob. Son utilisation n’a donc aucun effet sur notre contrat SLA. Azure Service Bus est un service distinct avec son propre contrat SLA. Si nous utilisions des files d’attente Service Bus, nous devrons prendre en compte un pourcentage de contrat SLA supplémentaire, et notre contrat SLA composite serait inférieur. Lorsque vous choisissez un service de file d’attente, veillez à comprendre l’impact de votre choix sur la disponibilité des applications. Pour plus d’informations, consultez la section Ressources .

Création de messages de file d’attente

Pour placer une tâche de correction dans la file d’attente, le serveur frontal web effectue les étapes suivantes :

  1. Créez un instance CloudQueueClient. Le CloudQueueClient instance est utilisé pour exécuter des requêtes sur le service de file d’attente.
  2. Créez la file d’attente, si elle n’existe pas encore.
  3. Sérialisez la tâche Corriger.
  4. Appelez CloudQueue.AddMessageAsync pour placer le message dans la file d’attente.

Nous allons effectuer ce travail dans le constructeur et SendMessageAsync la méthode d’une nouvelle FixItQueueManager classe.

public class FixItQueueManager : IFixItQueueManager
{
    private CloudQueueClient _queueClient;
    private IFixItTaskRepository _repository;

    private static readonly string fixitQueueName = "fixits";

    public FixItQueueManager(IFixItTaskRepository repository)
    {
        _repository = repository;
        CloudStorageAccount storageAccount = StorageUtils.StorageAccount;
        _queueClient = storageAccount.CreateCloudQueueClient();
    }

    // Puts a serialized fixit onto the queue.
    public async Task SendMessageAsync(FixItTask fixIt)
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        var fixitJson = JsonConvert.SerializeObject(fixIt);
        CloudQueueMessage message = new CloudQueueMessage(fixitJson);

        await queue.AddMessageAsync(message);
    }

    // Processes any messages on the queue.
    public async Task ProcessMessagesAsync()
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        while (true)
        {
            CloudQueueMessage message = await queue.GetMessageAsync();
            if (message == null)
            {
                break;
            }
            FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
            await _repository.CreateAsync(fixit);
            await queue.DeleteMessageAsync(message);
        }
    }
}

Ici, nous utilisons la bibliothèque Json.NET pour sérialiser le correctif au format JSON. Vous pouvez utiliser l’approche de sérialisation de votre choix. JSON présente l’avantage d’être lisible par l’homme, tout en étant moins détaillé que XML.

Le code de qualité de production ajoute une logique de gestion des erreurs, s’interrompt si la base de données devient indisponible, gère la récupération plus proprement, crée la file d’attente au démarrage de l’application et gère les messages « incohérents ». (Un message incohérent est un message qui ne peut pas être traité pour une raison quelconque. Vous ne voulez pas que les messages incohérents se trouvent dans la file d’attente, où le rôle de travail essaie continuellement de les traiter, d’échouer, de réessayer, d’échouer, etc.)

Dans l’application MVC frontale, nous devons mettre à jour le code qui crée une tâche. Au lieu de placer la tâche dans le référentiel, appelez la SendMessageAsync méthode indiquée ci-dessus.

public async Task<ActionResult> Create(FixItTask fixittask, HttpPostedFileBase photo)
{
    if (ModelState.IsValid)
    {
        fixittask.CreatedBy = User.Identity.Name;
        fixittask.PhotoUrl = await photoService.UploadPhotoAsync(photo);
        //previous code:
        //await fixItRepository.CreateAsync(fixittask);
        //new code:
        await queueManager.SendMessageAsync(fixittask);
        return RedirectToAction("Success");
    }
    return View(fixittask);
}

Traitement des messages de file d’attente

Pour traiter les messages dans la file d’attente, nous allons créer un service back-end. Le service back-end exécute une boucle infinie qui effectue les étapes suivantes :

  1. Obtenez le message suivant à partir de la file d’attente.
  2. Désérialisez le message dans une tâche de correction.
  3. Écrivez la tâche Corriger dans la base de données.

Pour héberger le service principal, nous allons créer un service cloud Azure qui contient un rôle de travail. Un rôle de travail se compose d’une ou plusieurs machines virtuelles qui peuvent effectuer un traitement back-end. Le code qui s’exécute dans ces machines virtuelles extrait les messages de la file d’attente à mesure qu’ils deviennent disponibles. Pour chaque message, nous allons désérialiser la charge utile JSON et écrire un instance de l’entité Corriger la tâche dans la base de données, en utilisant le même dépôt que celui utilisé précédemment dans la couche Web.

Les étapes suivantes montrent comment ajouter un projet de rôle de travail à une solution qui a un projet web standard. Ces étapes ont déjà été effectuées dans le projet Corriger que vous pouvez télécharger.

Tout d’abord, ajoutez un projet de service cloud à la solution Visual Studio. Cliquez avec le bouton droit sur la solution et sélectionnez Ajouter, puis Nouveau projet. Dans le volet gauche, développez Visual C# et sélectionnez Cloud.

Capture d’écran des étapes d’ajout d’un nouveau menu de projet dans le .Net Framework

Dans la boîte de dialogue Nouveau service cloud Azure , développez le nœud Visual C# dans le volet gauche. Sélectionnez Rôle de travail , puis cliquez sur l’icône de flèche droite.

La capture d’écran suivante montre une continuation de l’image précédente et montre les différentes sélections disponibles pour le service cloud Azure, en mettant en évidence la bonne.

(Notez que vous pouvez également ajouter un rôle web. Nous pouvons exécuter le serveur frontal Fix It dans le même service cloud au lieu de l’exécuter sur un site web Azure. Cela présente certains avantages en facilitant la coordination des connexions entre le serveur frontal et le serveur principal. Toutefois, pour simplifier cette démonstration, nous conservons le front-end dans une application web Azure App Service et nous exécutons uniquement le serveur principal dans un service cloud.)

Un nom par défaut est attribué au rôle de travail. Pour modifier le nom, placez la souris sur le rôle de travail dans le volet droit, puis cliquez sur l’icône en forme de crayon.

Capture d’écran montrant le projet de rôle de travail, avec les différentes assignations et comment modifier les noms.

Cliquez sur OK pour terminer la boîte de dialogue. Cela ajoute deux projets à la solution Visual Studio.

  • un projet Azure qui définit le service cloud, y compris les informations de configuration.
  • Projet de rôle de travail qui définit le rôle de travail.

Capture d’écran montrant un rôle de travail, définissant le rôle et montrant la liste des options de solution de projet « MyFixIt ».

Pour plus d’informations, consultez Création d’un projet Azure avec Visual Studio.

Dans le rôle de travail, nous interrogeons les messages en appelant la ProcessMessageAsync méthode de la FixItQueueManager classe que nous avons vue précédemment.

public class WorkerRole : RoleEntryPoint
{
    public override void Run()
    {
        Task task = RunAsync(tokenSource.Token);
        try
        {
            task.Wait();
        }
        catch (Exception ex)
        {
            logger.Error(ex, "Unhandled exception in FixIt worker role.");
        }
    }

    private async Task RunAsync(CancellationToken token)
    {
        using (var scope = container.BeginLifetimeScope())
        {
            IFixItQueueManager queueManager = scope.Resolve<IFixItQueueManager>();
            while (!token.IsCancellationRequested)
            {
                try
                {
                    await queueManager.ProcessMessagesAsync();
                }
                catch (Exception ex)
                {
                    logger.Error(ex, "Exception in worker role Run loop.");
                }
                await Task.Delay(1000);
            }
        }
    }
    // Other code not shown.
}

La ProcessMessagesAsync méthode vérifie si un message est en attente. Le cas échéant, il désérialise le message dans une FixItTask entité et enregistre l’entité dans la base de données. Il effectue une boucle jusqu’à ce que la file d’attente soit vide.

public async Task ProcessMessagesAsync()
{
    CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
    await queue.CreateIfNotExistsAsync();
    while (true)
    {
        CloudQueueMessage message = await queue.GetMessageAsync();
        if (message == null)
        {
            break;
        }
        FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
        await _repository.CreateAsync(fixit);
        await queue.DeleteMessageAsync(message);
    }
}

L’interrogation des messages de file d’attente entraîne de petits frais de transaction. Par conséquent, lorsqu’aucun message n’attend d’être traité, la méthode du rôle de RunAsync travail attend une seconde avant d’interroger à nouveau en appelant Task.Delay(1000).

Dans un projet web, l’ajout de code asynchrone peut améliorer automatiquement les performances, car IIS gère un pool de threads limité. Ce n’est pas le cas dans un projet de rôle de travail. Pour améliorer la scalabilité du rôle de travail, vous pouvez écrire du code multithread ou utiliser du code asynchrone pour implémenter la programmation parallèle. L’exemple n’implémente pas la programmation parallèle, mais montre comment rendre le code asynchrone afin que vous puissiez implémenter la programmation parallèle.

Résumé

Dans ce chapitre, vous avez vu comment améliorer la réactivité, la fiabilité et la scalabilité des applications en implémentant le modèle de travail centré sur la file d’attente.

Il s’agit du dernier des 13 modèles abordés dans ce livre électronique, mais il existe bien sûr de nombreux autres modèles et pratiques qui peuvent vous aider à créer des applications cloud réussies. Le dernier chapitre fournit des liens vers des ressources pour des rubriques qui n’ont pas été traitées dans ces 13 modèles.

Ressources

Pour plus d’informations sur les files d’attente, consultez les ressources suivantes.

Documentation :

Vidéo :

  • FailSafe : création de Services cloud évolutifs et résilients. Série de vidéos en neuf parties par Ulrich Homann, Marc Mercuri et Mark Simms. Présente des concepts de haut niveau et des principes architecturaux d’une manière très accessible et intéressante, avec des histoires tirées de l’expérience de l’équipe microsoft de conseil client (CAT) avec des clients réels. Pour une présentation du service stockage Azure et des files d’attente, consultez l’épisode 5 à partir de 35:13.