NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Sunday, 15 September 2024
//Less than a minute
Polly est une partie critique de la boîte à outils du développeur.NET. Il s'agit d'une bibliothèque qui vous permet de définir des politiques pour le traitement des exceptions et des requêtes dans votre application. Dans cet article, nous explorerons comment Annexe I utiliser Polly pour gérer les relevés dans cette application.
Bien que Polly récupère vraiment bien ce n'est pas tout ce qu'il peut faire, c'est vraiment une boîte à outils pour ajouter de la résilience à vos applications. Les deux font des appels vers des services extérieurs et à l'interne.
Ceux-ci sont tirés de la Page principale de Polly et sont les principaux modèles que vous pouvez utiliser avec Polly:
Dans cette application, j'utilise Polly en plusieurs endroits.
Pour démarrer mon service de traduction et vérifier les serveurs EasyNMT sont disponibles. Thsi me permet de vérifier que le service est disponible avant de commencer à 'offrir' le service de traduction dans mon application. Vous vous souviendrez que c'est utilisé pour mon éditeur 'toy' pour vous permettre de traduire le balisage et pour mon 'à la volée' blog poste moteur de traductionC'est ce que j'ai dit. Donc c'est critique je vérifie que EasyNMT n'est pas descendu (et permet d'attendre jusqu'à ce qu'il arrive ; ce qui peut prendre quelques secondes).
private async Task StartupHealthCheck(CancellationToken cancellationToken)
{
var retryPolicy = Policy
.HandleResult<bool>(result => !result) // Retry when Ping returns false (service not available)
.WaitAndRetryAsync(3, // Retry 3 times
attempt => TimeSpan.FromSeconds(5), // Wait 5 seconds between retries
(result, timeSpan, retryCount, context) =>
{
logger.LogWarning("Translation service is not available, retrying attempt {RetryCount}", retryCount);
});
try
{
var isUp = await retryPolicy.ExecuteAsync(async () =>
{
return await Ping(cancellationToken); // Ping to check if the service is up
});
if (isUp)
{
logger.LogInformation("Translation service is available");
TranslationServiceUp = true;
}
else
{
logger.LogError("Translation service is not available after retries");
await HandleTranslationServiceFailure();
TranslationServiceUp = false;
}
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while checking the translation service availability");
await HandleTranslationServiceFailure();
TranslationServiceUp = false;
}
}
Ici vous pouvez voir que nous avons mis en place une politique Polly Retry qui réessayera 3 fois avec une attente de 5 secondes entre chaque réessayer. Si le service n'est toujours pas disponible après les relevés, nous enregistrons une erreur et traitons l'échec en réglant le TranslationServiceUp
Drapeau à faux. Cela permet ensuite à tous les services utilisant le service de traduction de savoir qu'il n'est pas disponible.
J'utilise également Polly dans ma bibliothèque Umami.Net pour gérer les requêtes lors de la présentation des requêtes à l'API Umami. Il s'agit d'une partie critique de la bibliothèque car elle me permet de gérer tous les problèmes avec l'API et de réessayer la requête si nécessaire.
Ici, j'ai mis en place mon HttpClient
d'utiliser une politique de réessayer; dans ce cas, je vérifie HttpStatusCode.ServiceUnavailable
et de réessayer la demande si elle se produit. J'utilise aussi un Decorrelated Jitter Backoff
stratégie d'attente entre les rétries. Il s'agit d'une bonne stratégie à utiliser car elle permet d'éviter le problème du « troupeau dénudé » où tous les clients réessayent en même temps (même si ce n'est que moi :)). Cela peut aider à réduire la charge sur le serveur et améliorer les chances de succès de la requête.
var httpClientBuilder = services.AddHttpClient<AuthService>(options =>
{
options.BaseAddress = new Uri(umamiSettings.UmamiPath);
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler(RetryPolicyExtension.GetRetryPolicy());
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
var delay = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), 3);
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == HttpStatusCode.ServiceUnavailable)
.WaitAndRetryAsync(delay);
}
Utilisation d'une politique de ré-essai pour HttpClient
Les demandes sont un moyen important d'améliorer la fiabilité. Alors que nous aimons penser que nos services web sont toujours disponibles il ya toujours SOME temps d'arrêt (dans mon cas quand par exemple La Tour de Garde détecte une mise à jour est en attente et redémarre le conteneur Umami). Ainsi, avoir une politique de réessayer en place peut aider à s'assurer que votre application peut gérer ces situations gracieusement.
Une autre utilisation que je fais de Polly est quand il s'agit de charger et d'enregistrer des fichiers dans mon application. J'utilise un FileSystemWatcher
pour surveiller le répertoire où mes fichiers de balisage sont stockés. Quand un fichier est créé ou mis à jour, je charge le fichier et le traite. Cela peut être un problème si le fichier est toujours écrit à quand l'événement est déclenché. Donc j'utilise un RetryPolicy
pour gérer cette situation.
Ici vous pouvez voir que je gère le IOException
qui est lancé lorsque le fichier est en cours d'utilisation et réessayer l'opération. J'utilise un WaitAndRetryAsync
politique de réessayer l'opération 5 fois avec un retard entre chaque réessayer. Cela me permet de gérer la situation où le fichier est encore écrit et de réessayer l'opération jusqu'à ce qu'il réussisse.
Ce qui est critique ici, c'est que je jette 'up' à IOException
de ma part SavePost
méthode qui permet à la politique Polly de gérer la réessayer. C'est un bon modèle à suivre car il vous permet de gérer la logique de réessayer dans un endroit central et ne pas avoir à vous en soucier dans chaque méthode qui pourrait avoir besoin de réessayer une opération.
En général toujours gérer les exceptions où vous pouvez et vomir à un niveau plus élevé où vous pouvez les gérer de manière plus centralisée (ou les enregistrer). Cela peut aider à réduire la complexité de votre code et faciliter le traitement des exceptions d'une manière cohérente.
private async Task OnChangedAsync(WaitForChangedResult e)
{
...
var retryPolicy = Policy
.Handle<IOException>() // Only handle IO exceptions (like file in use)
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromMilliseconds(500 * retryAttempt),
(exception, timeSpan, retryCount, context) =>
{
activity?.Activity?.SetTag("Retry Attempt", retryCount);
// Log the retry attempt
logger.LogWarning("File is in use, retrying attempt {RetryCount} after {TimeSpan}", retryCount,
timeSpan);
});
...
// Use the Polly retry policy for executing the operation
await retryPolicy.ExecuteAsync(async () =>
{
...
var blogService = scope.ServiceProvider.GetRequiredService<IBlogService>();
await blogService.SavePost(blogModel);
...
});
...
}
Encore une fois, c'est un exemple de mon code interagissant avec un service extérieur, dans ce cas le système de fichiers. Où j'EXPÈTE certains types d'erreurs à se produire. Je les enregistre aussi en utilisant SérilogTraçage qui les envoie à Seq que THEN m'envoie un e-mail lorsque l'erreur est enregistrée afin que je puisse identifier tous les problèmes qui pourraient se produire.
Encore une fois, l'approche générale est de traiter les exceptions où vous pouvez, de les enregistrer lorsque vous ne pouvez pas et de vous assurer que vous avez un moyen de savoir ce qui se passe. Cela peut aider à s'assurer que votre application est résiliente et peut traiter tous les problèmes qui pourraient survenir.
Dans mon service d'email, j'utilise les deux CircuitBreaker
un modèle et une politique de réessayer. La politique de réessayer est utilisée pour traiter le cas où le service d'email n'est pas disponible et le disjoncteur est utilisé pour traiter le cas où le service d'email est occupé ou surchargé.
Ces deux éléments sont importants dans le cas des courriels; SMTP est un protocole relativement lent et potentiellement peu fiable.
Ici, je m'occupe de SmtpExceptions, où quand une erreur vient du service SMTP il réessayera d'abord trois fois avec un retard entre chaque réessayer. Si le service n'est toujours pas disponible après les relevés (et deux autres pour un nouvel envoi), le disjoncteur s'ouvrira et cessera d'envoyer des courriels pendant une minute. Cela peut aider à empêcher le service d'email d'être surchargé (et mon compte étant bloqué) et améliorer les chances que l'email soit envoyé avec succès.
// Initialize the retry policy
var retryPolicy = Policy
.Handle<SmtpException>() // Retry on any exception
.WaitAndRetryAsync(3, // Retry 3 times
attempt => TimeSpan.FromSeconds(2 * attempt),
(exception, timeSpan, retryCount, context) =>
{
logger.LogWarning(exception, "Retry {RetryCount} for sending email failed", retryCount);
});
// Initialize the circuit breaker policy
var circuitBreakerPolicy = Policy
.Handle<SmtpException>()
.CircuitBreakerAsync(
5,
TimeSpan.FromMinutes(1),
onBreak: (exception, timespan) =>
{
logger.LogError("Circuit broken due to too many failures. Breaking for {BreakDuration}", timespan);
},
onReset: () =>
{
logger.LogInformation("Circuit reset. Resuming email delivery.");
},
onHalfOpen: () =>
{
logger.LogInformation("Circuit in half-open state. Testing connection...");
});
_policyWrap = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
Ceci est ensuite utilisé dans ma boucle d'envoi d'email qui attend que de nouveaux messages soient ajoutés au canal puis essaye de les envoyer.
Cela utilise toute la functioanlity dans la politique enveloppée pour ajouter la résilience au processus d'envoi d'emails.
while (await _mailMessages.Reader.WaitToReadAsync(token))
{
BaseEmailModel? message = null;
try
{
message = await _mailMessages.Reader.ReadAsync(token);
// Execute retry policy and circuit breaker around the email sending logic
await _policyWrap.ExecuteAsync(async () =>
{
switch (message)
{
case ContactEmailModel contactEmailModel:
await _emailService.SendContactEmail(contactEmailModel);
break;
case CommentEmailModel commentEmailModel:
await _emailService.SendCommentEmail(commentEmailModel);
break;
}
});
_logger.LogInformation("Email from {SenderEmail} sent", message.SenderEmail);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception exc)
{
_logger.LogError(exc, "Couldn't send an e-mail from {SenderEmail}", message?.SenderEmail);
}
}
Polly est une bibliothèque puissante qui peut vous aider à ajouter de la résilience à vos applications. En utilisant Polly, vous pouvez gérer des retries, disjoncteurs, timeouts, limiteurs de taux, replis et couvertures dans votre application. Cela peut aider à s'assurer que votre application est fiable et peut traiter tous les problèmes qui pourraient survenir. Dans ce post, je viens vraiment de couvrir un aspect de Polly; les relevés, ce sont un mécanisme qui peut améliorer la résilience et la fiabilité de votre application. En utilisant Polly, vous pouvez gérer les relevés de manière cohérente et vous assurer que votre application peut gérer tous les problèmes qui pourraient survenir.