Back to "Utilizzo di Polly per le reti"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET C# Polly

Utilizzo di Polly per le reti

Sunday, 15 September 2024

Introduzione

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.

PollyCity name (optional, probably does not need a translation)

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:

  • Riprovare: Provare di nuovo se qualcosa fallisce. Questo può essere utile quando il problema è temporaneo e potrebbe andare via.
  • Interruttore di circuito: Smettila di provare se qualcosa è rotto o occupato. Questo può beneficiare evitando di perdere tempo e peggiorare le cose. Può anche supportare il sistema per recuperare.
  • Timeout: Arrenditi se qualcosa ci mette troppo. Questo può migliorare le tue prestazioni liberando spazio e risorse.
  • Limitatore di velocità: Limita quante richieste fai o accetti. Questo può consentire di controllare il carico e prevenire problemi o sanzioni.
  • Ripiegare: fare qualcos'altro se qualcosa fallisce. Questo può migliorare la vostra esperienza utente e mantenere il programma in funzione. Hedging: Fai più di una cosa allo stesso tempo e prendi quella più veloce. Questo può rendere il vostro programma più veloce e più reattivo.

Come uso Polly

In questa applicazione uso Polly in più luoghi.

SfondoTranslateService

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.

graph LR A[Start Health Check] --> B[Define Retry Policy] B --> C[Retry Policy: Retry 3 times] C --> D[Wait 5 seconds between retries] D --> E[Ping Translation Service] E --> F{Ping successful?} F -- Yes --> G[Log: Translation service is available] G --> H[Set TranslationServiceUp = true] F -- No --> I[Log: Translation service not available] I --> J[Check retry count] J -- Retry Limit Reached --> K[Log: Translation service not available after retries] K --> L[HandleTranslationServiceFailure] L --> M[Set TranslationServiceUp = false] J -- Retry Again --> E E --> N{Exception Occurs?} N -- Yes --> O[Log: Error occurred] O --> L

Umami.NetCity name (optional, probably does not need a translation)

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.

FileSystemWatcher

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.

Servizio email

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);
            }
        }

In conclusione

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.

logo

©2024 Scott Galloway