Fermer

mars 14, 2019

Utilisation de Polly pour la résilience .NET avec .NET Core


Découvrez comment le projet Polly, un framework .NET open source fournissant des modèles et des blocs de construction pour la tolérance aux pannes et la résilience dans les applications, peut être utilisé avec .NET Core.

Traitement des erreurs et reprise fiable en cas d'erreur sont le talon d’Achille de nombreux projets logiciels. Les applications qui fonctionnaient sans à-coups se transformaient soudainement en cauchemars chaotiques dès que la connectivité du réseau se détériorait ou que l'espace disque s'épuisait. Les logiciels professionnels se démarquent en traitant exactement ces cas extrêmes (à un certain taux d'adoption, ces «cas extrêmes» deviennent même normaux), en les attendant et en les traitant avec élégance. Pouvoir compter sur un cadre existant et dur comme bataille pour de tels scénarios rend les choses encore plus faciles.

Enter Polly

C’est là que le projet Polly entre en jeu. Polly est un framework .NET open source qui fournit des modèles et des blocs de construction pour la tolérance aux pannes et la résilience dans les applications.

 the-polly-project "title =" the-polly-project "/></p data-recalc-dims=

Site Web de Polly Project [19659004] Polly est entièrement open source, disponible pour différents types de .NET à partir de .NET 4.0 et .NET Standard 1.1 et peut facilement être ajouté à n’importe quel projet via le package Polly NuGet. Pour les applications .NET Core, procédez comme suit: Ligne de commande utilisant la commande CLI de la commande DOTNET.

 dotnet add package Polly

Ou en ajoutant un PackageReference au fichier .csproj (au moment de la rédaction du présent document, la dernière version était 6.1.2).

 

Lorsque vous utilisez Visual Studio, “Gérer les paquets NuGet…” est le moyen le plus rapide.

 polly-nuget-vs "title =" polly-nuget-vs "/></p data-recalc-dims=

Ajout de la référence Polly NuGet dans Visual Studio

Maintenant que Polly a été ajouté au projet, la question qui se pose est de savoir comment et dans quels scénarios peut-il être utilisé? Comme c'est si souvent le cas, l'explication est plus simple en utilisant du code réel et un exemple pratique. [19659014] Pour simplifier les choses, supposons que nous ayons une application qui conserve en permanence les données ou les paramètres en arrière-plan en les écrivant sur le disque (méthode appelée PersistApplicationData.

 private void PersistApplicationData () 

Comme cette méthode accède au système de fichiers, elle échoue de temps en temps.Le disque peut être plein, les fichiers peuvent être verrouillés de manière inattendue par des services d'indexation ou un logiciel anti-virus, les droits d'accès ont peut-être été révoqués … fondamentalement, tout peut arriver ici. l’accès doit toujours être traité comme une dépendance externe échappant au contrôle d’une application. Par conséquent, il faut au minimum un bloc catch try.

La prochaine question évidente est de savoir quels types d'exceptions doivent être capturés dans le bloc catch. La classe de base Exception couvre tous les cas possibles, mais elle est peut-être aussi trop générique. Des exceptions comme NullReferenceException ou AccessViolationException impliquent généralement de graves problèmes dans la logique de l’application et ne doivent probablement pas être gérées correctement. Donc, attraper des exceptions spécifiques comme IOException ou InvalidOperationException pourrait être la meilleure option ici. Nous nous retrouvons donc avec deux blocs de capture pour cet exemple.

Puisque nous ne voulons pas ignorer complètement ces exceptions, au moins un code de journalisation doit être mis en place. Nous devons donc dupliquer un appel à une méthode de journalisation dans les blocs catch.

Lors de la prochaine étape, nous devons réfléchir à la question de savoir si ou comment l'application doit continuer si une exception réelle s'est produite. Si nous supposons que nous voulons implémenter un motif de nouvelle tentative, une boucle supplémentaire en dehors du bloc catch catch est nécessaire pour pouvoir répéter l'appel à PersistApplicationData . Cela peut être une boucle infinie ou une boucle qui se termine après un nombre spécifique de tentatives. Dans tous les cas, nous devons manuellement veiller à ce que la boucle soit sortie en cas d'appel réussi.

Enfin, nous devrions également considérer que le risque d'échec est vraiment élevé si un appel ultérieur à PersistApplicationData se reproduit immédiatement. Un mécanisme de limitation est probablement nécessaire. La méthode la plus simple consiste à appeler Thread.Sleep à l'aide d'un nombre codé en dur de millisecondes. Ou nous pourrions utiliser une approche incrémentielle en prenant en compte le nombre de boucles en cours.

En prenant toutes ces considérations en compte, un simple appel de méthode est rapidement devenu une structure de ligne de plus de 20 comme ceci.

 private void GuardPersistApplicationData ()
{
  const int RETRY_ATTEMPTS = 5;
  for (var i = 0; i < RETRY_ATTEMPTS; i++) {
    try
    {
      Thread.Sleep(i * 100);
      // Here comes the call, we *actually* care about.
      PersistApplicationData(); 
      // Successful call => boucle de sortie.
      Pause;
    }
    catch (IOException e)
    {
      Log (e);
    }
    catch (UnauthorizedAccessException e)
    {
      Log (e);
    }
  }
}

Cet exemple simple illustre le problème principal en matière de code tolérant aux pannes et résilient: il est souvent difficile à lire et même difficile à lire car il occulte la logique d'application réelle.

Code résilient et tolérant aux pannes est nécessaire … mais pas toujours "beau" à regarder.

La solution évidente à ce problème est constituée par des blocs de code réutilisables de manière générale qui gèrent les problèmes identifiés. Au lieu de réinventer la roue et d'écrire ces blocs de codes à plusieurs reprises, une bibliothèque comme Polly devrait être notre arme naturelle de choix.

Polly fournit des blocs de construction pour les cas d'utilisation (et bien d'autres) que nous avons identifiés auparavant dans le formulaire. des politiques. Examinons donc ces politiques plus en détail et comment elles peuvent être utilisées pour l'exemple ci-dessus.

Retry Forever

La politique la plus élémentaire fournie par Polly est RetryForever qui fait exactement ce que son nom l'indique. Un morceau de code spécifique (ici: PersistApplicationData) est exécuté à maintes reprises jusqu'à ce qu'il réussisse (c'est-à-dire qu'il ne lève pas d'exception). La politique est créée et appliquée en définissant d'abord les exceptions attendues via un appel à Policy.Handle . Ensuite, RetryForever spécifie la stratégie réelle utilisée et Execute attend le code qui sera gardé par la stratégie.

 Policy.Handle  ()
  .RetryForever ()
  .Execute (PersistApplicationData);

Encore une fois, nous ne voulons pas traiter de manière générique toutes les exceptions possibles, mais des types spécifiques. Cela peut être fait en fournissant les arguments de type et en les combinant en utilisant la méthode ou .

 Policy.Handle  (). Ou  ()
  .RetryForever ()
  .Execute (PersistApplicationData);

Par conséquent, capturer ces exceptions en silence est une très mauvaise pratique et nous pouvons donc utiliser une surcharge de RetryForever qui attend une expression appelée en cas d'exception.

 Policy.Handle  () .Ou  ()
  .RetryForever (e => Log (e.Message))
  .Execute (PersistApplicationData);

Nouvelle tentative n

La politique RetryForever couvrait déjà une partie des besoins que nous avions initialement définis, mais le concept d'un nombre potentiellement infini d'appels vers PersistApplicationData n'est pas ce qu'il est. nous avions en tête. Nous pourrions donc opter pour la politique Retry à la place. Retry se comporte de manière très similaire à RetryForever à la différence essentielle qu'il attend un argument numérique spécifiant le nombre réel de tentatives de relance avant d'abandonner.

 Policy.Handle  ()
  .Retry (10)
  .Execute (PersistApplicationData);

De même, il existe également une surcharge de Retry qui permet à l'appelant de gérer une exception éventuelle et de recevoir en outre un argument int spécifiant le nombre de tentatives d'appel déjà effectuées.

 Policy.Handle  ()
  .Retry (10, (e, i) => Log ($ "Erreur '{e.Message}' à la nouvelle tentative # {i}"))
  .Execute (PersistApplicationData);

Wait and Retry

La dernière condition encore non remplie par rapport à l'exemple initial est la possibilité de limiter l'exécution du mécanisme de nouvelle tentative, en espérant que la ressource fragmentée qui a initialement provoqué ce problème aurait récupéré à ce jour. ] Là encore, Polly fournit une stratégie spécifique pour ce cas d’utilisation appelé WaitAndRetry . La surcharge la plus simple de WaitAndRetry s'attend à une collection d'instances Timespan et la taille de cette collection dicte implicitement le nombre de tentatives. Par conséquent, les instances individuelles Timespan spécifient le temps d'attente avant chaque appel Execute .

 Policy.Handle  ()
  .WaitAndRetry (new [] {TimeSpan.FromMilliseconds (100), TimeSpan.FromMilliseconds (200)})
  .Execute (PersistApplicationData);

Si nous voulions calculer ces temps d'attente de manière dynamique, une autre surcharge de WaitAndRetry est disponible.

 Policy.Handle  ()
  .WaitAndRetry (5, count => TimeSpan.FromSeconds (count))
  .Execute (PersistApplicationData);

Une quantité infinie de tentatives utilisant un temps d'attente dynamique est également possible avec WaitAndRetryForever .

 Policy.Handle  ()
  .WaitAndRetryForever (count => TimeSpan.FromSeconds (count))
  .Execute (PersistApplicationData);

Circuit Breaker

La dernière politique que nous souhaitons examiner est légèrement différente de celles que nous avons connues jusqu'à présent. CircuitBreaker agit comme son prototype, qui interrompt le flux d'électricité. Les contreparties logicielles des courants de défaut ou des courts-circuits sont des exceptions, et cette stratégie peut être configurée de manière à ce qu'un certain nombre d'exceptions «interrompent» le flux de l'application. Cela a pour effet que le code «protégé» ( PersistApplicationData ) ne sera tout simplement plus appelé, dès qu'un seuil d'exceptions donné aura été atteint. De plus, un intervalle peut être spécifié, après quoi le CircuitBreaker est restauré et le flux d'applications est restauré à nouveau.

En raison de ce modèle, cette règle est généralement utilisée en la configurant initialement et en stockant le réel Policy dans une variable. Cette instance assure le suivi des appels ayant échoué et de l'intervalle de récupération et est utilisée pour exécuter l'appel Execute dans un endroit différent.

   .Handle  (). Ou  ()
  .CircuitBreaker (5, TimeSpan.FromMinutes (2));
  // ...
  policy.Execute (PersistApplicationData);

Mais attendez, c’est plus!

Les stratégies décrites ci-dessus ne donnent qu’un aperçu des fonctionnalités polyvalentes fournies par Polly. Chacune de ces politiques est par exemple également disponible en version asynchrone ( RetryForeverAsync RetryAsync WaitAndRetryAsync CircuitBreakerAsync ) tout en conservant la même facilité d'utilisation sous la forme synchronique

La seule politique CircuitBreaker offre à elle seule de multiples modes de configuration supplémentaires, qui sont documentés en détail dans le référentiel GitHub. En général, ce référentiel, sa documentation et les exemples constituent un excellent point de départ pour se familiariser avec les règles fournies par Polly et les concepts de base de la résilience et du traitement des incidents transitoires. J'espère que cela servira d'inspiration pour se débarrasser du code personnalisé de gestion des erreurs conçu à la main, ou même mieux, évitera d'écrire ce genre de code dans les projets futurs et d'utiliser plutôt les pouvoirs de Polly.


Les commentaires sont désactivés. en mode de prévisualisation.




Source link