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
PollyCity name (optional, probably does not need a translation) è una parte critica di qualsiasi toolkit.NET sviluppatore. Si tratta di una libreria che consente di definire politiche per la gestione di eccezioni e ripetizioni nella tua applicazione. In questo articolo, esploreremo come I utilizzare Polly per gestire i tentativi in questa applicazione.
Mentre Polly fa le riletture davvero bene non è tutto quello che può fare, è davvero un toolkit per aggiungere resilienza alle vostre applicazioni. Entrambi fanno chiamate a servizi esterni e internamente.
Questi sono presi dal Pagina principale di Polly e sono i modelli principali che è possibile utilizzare con Polly:
In questa applicazione uso Polly in più luoghi.
Per avviare il mio servizio di traduzione e controllare i server EasyNMT sono disponibili. Thsi mi permette di controllare il servizio è disponibile prima di iniziare a 'offrire' il servizio di traduzione nella mia app. Ricorderai che e' usato entrambi per... il mio editor 'giocattolo' per permettervi di tradurre markdown e per il mio 'al volo' blog post motore di traduzione. Quindi è critico che controllo EasyNMT non è andato giù (e attivare l'attesa fino a quando arriva; che può richiedere alcuni secondi).
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;
}
}
Qui potete vedere che abbiamo impostato una politica Polly Retry che riproverà 3 volte con un'attesa di 5 secondi tra ogni riprova. Se il servizio non è ancora disponibile dopo i tentativi, registriamo un errore e gestiamo il guasto impostando il TranslationServiceUp
Bandiera a falso. Questo permette quindi a tutti i servizi che utilizzano il servizio di traduzione di sapere che non è disponibile.
Uso anche Polly nella mia libreria Umami.Net per gestire i ripetizioni quando faccio richieste all'API Umami. Questa è una parte critica della libreria in quanto mi permette di gestire qualsiasi problema con l'API e riprovare la richiesta se necessario.
Qui ho messo su il mio HttpClient
per utilizzare una politica di riprova; in questo caso sto controllando per un HttpStatusCode.ServiceUnavailable
e riprovare la richiesta se si verifica. Sto anche usando un Decorrelated Jitter Backoff
la strategia di aspettare tra le ricompense. Questa è una buona strategia da usare in quanto aiuta ad evitare il problema del mandria 'thundering' dove tutti i clienti riprovano allo stesso tempo (anche se sono solo io:)). Questo può aiutare a ridurre il carico sul server e migliorare le possibilità di successo della richiesta.
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);
}
Utilizzando una politica di riprova per HttpClient
Le richieste sono un modo importante per migliorare l'affidabilità. Mentre ci piace pensare che i nostri servizi web sono sempre disponibili c'è sempre qualche downtime (nel mio caso quando per esempio Torre di guardia rileva un aggiornamento è in attesa e riavvia il contenitore Umami). Quindi avere una politica di riprova in atto può aiutare a garantire che la vostra applicazione può gestire queste situazioni con grazia.
Un altro uso che faccio di Polly è quando si tratta di caricare e salvare i file nella mia applicazione. Io uso un FileSystemWatcher
per monitorare la directory in cui vengono memorizzati i miei file markdown. Quando un file viene creato o aggiornato, lo carico e lo processo. Questo può essere un problema se il file viene ancora scritto quando l'evento viene attivato. Quindi uso un RetryPolicy
per gestire questa situazione.
Qui puoi vedere che me ne occupo io. IOException
che viene lanciato quando il file è in uso e riprovare l'operazione. Io uso un WaitAndRetryAsync
politica di riprovare l'operazione 5 volte con un ritardo tra ogni riprova. Questo mi permette di gestire la situazione in cui il file è ancora in fase di scrittura e riprovare l'operazione fino a quando non riesce.
Ciò che è critico qui è che io lancio 'up' a IOException
dal mio SavePost
metodo che permette alla politica Polly di gestire la riprova. Questo è un buon modello da seguire in quanto consente di gestire la logica di riprovare in un posto centrale e non devono preoccuparsi di esso in ogni metodo che potrebbe essere necessario per riprovare un'operazione.
In generale sempre gestire le eccezioni dove è possibile e vomito ad un livello superiore in cui è possibile gestirli in modo più centralizzato (o registrarli). Questo può aiutare a ridurre la complessità del codice e rendere più facile gestire le eccezioni in modo coerente.
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);
...
});
...
}
Ancora una volta questo è un esempio del mio codice che interagisce con un servizio esterno, in questo caso il filesystem. Dove mi aspetto che si verifichino alcuni tipi di errore. Li registro anche usando SerilogTracingCity name (optional, probably does not need a translation) che li invia a Seq che poi mi manda un'e-mail quando l'errore è registrato in modo da poter identificare eventuali problemi che potrebbero verificarsi.
Ancora una volta, l'approccio generale è quello di gestire le eccezioni dove è possibile, registrarli quando non è possibile e assicurarsi di avere un modo per sapere cosa sta succedendo. Questo può aiutare a garantire che la vostra applicazione è resiliente e in grado di gestire tutti i problemi che potrebbero verificarsi.
Nel mio servizio di posta elettronica uso entrambi i CircuitBreaker
zione e una politica di riprova. La politica di riprova viene utilizzata per gestire il caso in cui il servizio email non è disponibile e l'interruttore è utilizzato per gestire il caso in cui il servizio email è occupato o sovraccaricato.
Entrambi questi sono importanti nel caso di e-mail; SMTP è un protocollo relativamente lento e potenzialmente inaffidabile.
Qui mi occupo di SmtpExceptions, dove quando un errore viene dal servizio SMTP si ritenterà prima tre volte con un ritardo tra ogni riprova. Se il servizio non è ancora disponibile dopo i tentativi (e altri due per un nuovo invio), l'interruttore si aprirà e smetterà di inviare e-mail per un minuto. Questo può aiutare a evitare che il servizio email sia sovraccaricato (e che il mio account sia bloccato) e a migliorare le possibilità che l'email venga inviata con successo.
// 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);
Questo viene poi utilizzato nel mio ciclo di invio e-mail che attende l'aggiunta di nuovi messaggi al canale quindi tenta di inviarli.
Questo utilizza tutta la funzionalità nella politica avvolta per aggiungere resilienza al processo di invio e-mail.
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 è una libreria potente che può aiutarti ad aggiungere resilienza alle tue applicazioni. Utilizzando Polly è possibile gestire reticoli, interruttori di circuito, timeout, limitatori di velocità, ripiegamenti e copertura nella vostra applicazione. Questo può aiutare a garantire che l'applicazione è affidabile e in grado di gestire tutti i problemi che potrebbero verificarsi. In questo post ho davvero appena trattato un aspetto di Polly; ripetizioni, questi sono un meccanismo che può migliorare la resilienza e l'affidabilità della vostra applicazione. Utilizzando Polly è possibile gestire i tentativi in modo coerente e garantire che la vostra applicazione può gestire tutti i problemi che potrebbero verificarsi.