Back to "Använda Polly för retries"

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

Använda Polly för retries

Sunday, 15 September 2024

Inledning

Polly Ordförande är en kritisk del av någon.NET utvecklares verktygslåda. Det är ett bibliotek som låter dig definiera policyer för hantering av undantag och retries i din ansökan. I den här artikeln kommer vi att undersöka hur man I använda Polly för att hantera retries i detta program.

Polly Ordförande

Medan Polly gör retries riktigt bra det är inte allt det kan göra, det är verkligen en verktygslåda för att lägga till motståndskraft till dina program. Både samtal till externa tjänster och internt.

Dessa är tagna från Polly huvudsida och är de viktigaste mönster du kan använda med Polly:

  • Försök igen om något misslyckas. Detta kan vara användbart när problemet är tillfälligt och kan försvinna.
  • Circuit Breaker: Sluta försöka om något är trasigt eller upptagen. Detta kan vara till nytta för dig genom att du undviker att slösa bort tid och göra saker och ting värre. Det kan också stödja systemet för återhämtning.
  • Timeout: Ge upp om något tar för lång tid. Detta kan förbättra din prestation genom att frigöra utrymme och resurser.
  • Betygsbegränsare: Begränsa hur många förfrågningar du gör eller accepterar. Detta kan göra det möjligt för dig att kontrollera belastningen och förhindra problem eller påföljder.
  • Reträtt: Gör något annat om något misslyckas. Detta kan förbättra din användarupplevelse och hålla programmet fungerar. Säkring: Gör mer än en sak samtidigt och ta den snabbaste. Detta kan göra ditt program snabbare och mer lyhörd.

Hur jag använder Polly

I den här applikationen använder jag Polly på flera ställen.

BakgrundTranslateService

För att starta upp min översättningstjänst och kontrollera EasyNMT servrar finns tillgängliga. Thsi låter mig kontrollera att tjänsten är tillgänglig innan jag börjar erbjuda översättningstjänsten i min app. Du kommer säkert ihåg att det här båda används för min redaktör 'leksak' så att du kan översätta markdown och för min "i farten" blogg inlägg översättning motor....................................... Så det är viktigt jag kontrollera EasyNMT har inte gått ner (och aktivera vänta tills det kommer upp; som kan ta några sekunder).

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

Här kan du se att vi sätter upp en Polly Retry policy som kommer att försöka 3 gånger med en 5-sekunders väntan mellan varje försök. Om tjänsten fortfarande inte är tillgänglig efter retries, loggar vi ett fel och hanterar felet genom att ställa in TranslationServiceUp Flagga till falskt. Detta gör det möjligt för alla tjänster som använder översättningstjänsten att veta att den inte är tillgänglig.

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

Ummami.Net

Jag använder också Polly i mitt Umami.Net bibliotek för att hantera retries när jag gör förfrågningar till Umami API. Detta är en kritisk del av biblioteket eftersom det gör det möjligt för mig att hantera eventuella problem med API:et och försök om begäran om nödvändigt.

Här sätter jag upp min HttpClient att använda en Retry Policy; i detta fall jag kollar efter en HttpStatusCode.ServiceUnavailable och försök igen begäran om det inträffar. Jag använder också en Decorrelated Jitter Backoff strategi för att vänta mellan retries. Detta är en bra strategi att använda eftersom det hjälper till att undvika problemet med att "ringa hjorden" där alla kunder försöker på samma gång (även om det bara är jag :)). Detta kan bidra till att minska belastningen på servern och förbättra chanserna att begäran lyckas.

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

Använda en ny policy för att HttpClient Begäran är ett viktigt sätt att förbättra tillförlitligheten. Medan vi gillar att tro att våra webbtjänster alltid är tillgängliga finns det alltid NÅGRA stilleståndstider (i mitt fall när till exempel Vakttornet upptäcker att en uppdatering pågår och startar om Umami-behållaren). Så att ha en försök politik på plats kan hjälpa till att se till att din ansökan kan hantera dessa situationer graciöst.

FileSystemwatcher

En annan användning jag gör av Polly är när jag hanterar att ladda och spara filer i min ansökan. Jag använder en FileSystemWatcher för att övervaka katalogen där mina markdown-filer lagras. När en fil skapas eller uppdateras laddar jag filen och bearbetar den. Detta kan vara ett problem om filen fortfarande skrivs till när händelsen utlöses. Så jag använder en RetryPolicy för att hantera denna situation.

Här ser du att jag tar hand om IOException som kastas när filen används och försök igen. Jag använder en WaitAndRetryAsync policy för att prova operationen 5 gånger med en fördröjning mellan varje försök. Detta gör att jag kan hantera situationen där filen fortfarande skrivs till och prova operationen tills den lyckas.

Vad som är viktigt här är att jag kastar upp till IOException från min SavePost Metod som gör det möjligt för Polly-politiken att hantera försöket. Detta är ett bra mönster att följa eftersom det gör att du kan hantera retry logik på en central plats och inte behöver oroa sig för det i varje metod som kan behöva prova en operation.

Allmänt alltid hantera undantag där du kan och kräkas till en högre nivå där du kan hantera dem på ett mer centraliserat sätt (eller logga dem). Detta kan bidra till att minska komplexiteten i din kod och göra det lättare att hantera undantag på ett konsekvent sätt.

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

Återigen detta är ett exempel på min kod interagerar med en extern tjänst, i detta fall filsystemet. Där jag FÖRVÄNTAR vissa feltyper att inträffa. Jag loggar också dessa med SerilogTracing som skickar dem till Seq som THEN skickar mig ett e-postmeddelande när felaren är inloggad så att jag kan identifiera eventuella problem som kan uppstå.

Återigen, den allmänna metoden är att hantera undantag där du kan, logga dem när du inte kan och se till att du har ett sätt att veta vad som händer. Detta kan bidra till att din applikation är motståndskraftig och kan hantera eventuella problem som kan uppstå.

E-posttjänst

I min e-posttjänst använder jag båda CircuitBreaker Mönster och en ny policy. Återförsökspolicyn används för att hantera fallet där e-posttjänsten inte är tillgänglig och brytaren används för att hantera fallet där e-posttjänsten är upptagen eller överbelastad.

Båda dessa är viktiga när det gäller e-post; SMTP är en relativt långsam och potentiellt opålitlig protokoll.

Här hanterar jag SmtpExceptioner, där när ett fel kommer från SMTP-tjänsten kommer det först att försöka tre gånger med en fördröjning mellan varje försök. Om tjänsten fortfarande inte är tillgänglig efter retries (och två till för en ny sändning), kommer brytaren att öppna och sluta skicka e-post för en minut. Detta kan bidra till att förhindra att e-posttjänsten överbelastas (och mitt konto blockeras) och förbättra chanserna att e-post skickas framgångsrikt.

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

Detta används sedan i min e-post Skicka loop som väntar på att nya meddelanden ska läggas till kanalen och sedan försöker skicka dem.

Detta använder all functioanlity i den inslagna politiken för att lägga till motståndskraft till e-postutskicksprocessen.

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

Slutsatser

Polly är ett kraftfullt bibliotek som kan hjälpa dig att öka motståndskraften i dina applikationer. Genom att använda Polly kan du hantera retries, brytare, timeouts, hastighetsbegränsare, reserv och säkring i din applikation. Detta kan bidra till att din ansökan är tillförlitlig och kan hantera eventuella problem som kan uppstå. I det här inlägget täckte jag egentligen just en aspekt av Polly; retries, dessa är en mekanism som kan förbättra motståndskraften och tillförlitligheten i din ansökan. Genom att använda Polly kan du hantera retries på ett konsekvent sätt och se till att din ansökan kan hantera eventuella problem som kan uppstå.

logo

©2024 Scott Galloway