Återförsöksmönster

Gör så att ett program kan hantera tillfälliga fel vid försök att ansluta till en tjänst eller nätverksresurs, genom att transparent försöka utföra den misslyckade åtgärden igen. Detta kan förbättra stabiliteten i programmet.

Kontext och problem

Ett program som kommunicerar med element som körs i molnet måste kunna känna av tillfälliga problem som uppstår i den här miljön. Fel omfattar tillfällig avbruten nätverksanslutning till komponenter och tjänster, att en tjänst är tillfälligt otillgänglig eller att tidsgränser uppnås när en tjänst är upptagen.

Sådana fel brukar vara självkorrigerande, och om åtgärden som utlöste felet upprepas efter en lämplig fördröjning kan den troligtvis genomföras. I en databastjänst som bearbetar ett stort antal samtidiga begäranden kan till exempel en begränsningsstrategi implementeras, som tillfälligt avvisar begäranden tills belastningen har minskat. Ett programs åtkomstförsök till databasen kan misslyckas, men kan lyckas vid nästa försök efter en viss fördröjning.

Lösning

Det är inte ovanligt att det förekommer tillfälliga fel i molnet. Ett program bör vara utformat så att det kan hantera fel på ett effektivt och transparent sätt. Detta minskar effekterna som fel kan ha på de verksamhetsaktiviteter ett program utför.

Om ett program upptäcker ett fel när en begäran skickas till en fjärrtjänst kan det hantera felet med följande åtgärder:

  • Avbryt. Om det indikeras att felet inte är tillfälligt eller troligen inte kommer att lyckas om försöket upprepas, bör programmet avbryta åtgärden och rapportera undantaget. Ett exempel är autentiseringsfel som orsakats av att ogiltiga autentiseringsuppgifter har angetts. Det kommer troligen inte att lyckas oavsett hur många försök som görs.

  • Försök igen. Om de rapporterade felen är ovanliga eller sällsynta kan de ha orsakats av ovanliga omständigheter, som ett nätverkspaket som skadats under överföringen. I sådana fall kan programmet försöka omedelbart igen eftersom samma fel troligen inte kommer att upprepas och begäran nu kan genomföras.

  • Försök igen efter fördröjning . Om felet orsakas av vanligare anslutnings- eller upptaget-fel kan det ta en kort stund för nätverket eller tjänsten att åtgärda anslutningsproblemen eller kvarvarande arbete. Programmet bör vänta en lämplig tidsperiod innan begäran skickas igen.

För vanliga tillfälliga fel bör perioden mellan försöken anpassas så att begäran från olika instanser av programmet sprids ut så jämnt som möjligt. Då minskar risken för att en upptagen tjänst ska fortsätta att vara överbelastad. Om flera instanser av ett program ständigt överbelastar en tjänst med återförsök tar det längre tid för tjänsten att återställas.

Om en begäran fortfarande misslyckas kan programmet vänta innan ett nytt försök görs. Om det behövs kan processen upprepas med längre perioder mellan återförsöken, tills högsta antal försök för begäran har gjorts. Fördröjningen kan ökas stegvis eller exponentiellt, beroende på vilken typ av fel det handlar om och sannolikheten för att det korrigeras under denna period.

Följande diagram visar hur ett åtgärdsanrop i en värdbaserad tjänst fungerar med det här mönstret. Om begäran misslyckas efter ett fördefinierat antal försök, bör programmet hantera felet som ett undantag och utföra tillämpliga åtgärder.

Bild 1 – Anropa en åtgärd i en värdbaserad tjänst med återförsöksmönstret.

I programmet ska alla försök att komma åt en fjärrtjänst ingå i kod som implementerar en återförsöksprincip, och som matchar en av strategierna som anges ovan. Begäranden som skickas till olika tjänster kan behöva följa olika principer. Vissa leverantörer tillhandahåller bibliotek som implementerar återförsöksprinciper, där programmet kan ange det maximala antalet återförsök, tiden mellan återförsöken och andra parametrar.

Information om fel och misslyckade åtgärder ska loggas i ett program. Den här informationen är användbar för operatorerna. För att undvika att operatörer översvämmas med aviseringar om åtgärder där senare försök har lyckats är det bäst att logga tidiga fel som informationsposter och endast felet för det sista av återförsöken som ett faktiskt fel. Här är ett exempel på hur den här loggningsmodellen skulle se ut som.

Om en tjänst ofta är otillgänglig eller upptagen brukar det bero på att dess resurser är slut. Du kan minska frekvensen för sådana fel genom att skala tjänsten. Anta att en databastjänst ständigt överbelastas. Då kan det vara ett alternativ att partitionera databasen så att belastningen fördelas på flera servrar.

Microsofts Entity Framework innehåller funktioner för återförsök av databasåtgärder. De flesta Azure-tjänster och klient-SDK:er har en återförsöksmekanism. Mer information hittar du i vägledningen om återförsök för specifika tjänster.

Problem och överväganden

När du bestämmer hur det här mönstret ska implementeras bör du överväga följande punkter.

Återförsöksprincipen ska anpassas efter verksamhetsbehoven för programmet och typen av fel. För vissa icke-kritiska åtgärder är det bättre att misslyckas snabbt än att försöka igen flera gånger och därmed påverka programmets dataflöde. I till exempel ett interaktivt webbprogram som har åtkomst till en fjärrtjänst är det bättre att misslyckas efter ett mindre antal återförsök med bara en kort fördröjning mellan återförsök och visa ett lämpligt meddelande för användaren (till exempel "försök igen senare"). För batchprogram kan det vara lämpligare att öka antalet återförsök med fördröjningar som ökar exponentiellt mellan försöken.

En aggressiv återförsöksprincip med minimal fördröjning mellan försöken och ett stort antal återförsök kan ha negativ inverkan på en belastad tjänst som nästan har nått sin kapacitet. En sådan återförsöksprincip kan också påverka svarstiden för programmet om det försöker utföra en åtgärd som misslyckas upprepade gånger.

Om en begäran fortsätter att misslyckas efter många återförsök är det bättre att programmet förhindrar ytterligare begäranden till samma resurs och istället rapporterar felet omedelbart. När perioden upphör kan programmet prova med en eller två begäranden för att fastställa om dessa genomförs. Mer information om den här strategin finns på sidan om kretsbrytarmönstret.

Fastställ om åtgärden är idempotent. Om så är fallet går det bra att försöka igen. Om så inte är fallet kan återförsök medföra att åtgärden körs mer än en gång, med oväntade sidoeffekter. En tjänst kan till exempel ta emot en begäran och bearbeta den, men inte skicka ett svar. I sådana fall kan återförsökslogiken kanske skicka begäran igen, förutsatt att den första inte mottogs.

En begäran till en tjänst kan misslyckas av olika orsaker och kan således utgöra olika typer av undantag beroende på typen av fel. Vissa undantag indikerar ett fel som kan lösas snabbt, medan andra indikerar att felet är mer långvarigt. Det är bra om återförsöksprincipens tid mellan återförsök justeras utifrån typen av undantag.

Ta med i beräkningen hur återförsök för en åtgärd som är en del av en transaktion påverkar hela transaktionskonsekvensen. Finjustera återförsöksprincipen för transaktionsåtgärder för största möjlighet att lyckas och minska behovet av att ångra alla transaktionsstegen.

Se till att all återförsökskod har testats mot en mängd olika felvillkor. Kontrollera att den inte har allvarlig inverkan på programmets prestanda eller tillförlitlighet, orsakar hög belastning på tjänster och resurser, eller genererar konkurrenstillstånd eller flaskhalsar.

Implementera endast återförsökslogik där hela sammanhanget för en misslyckad åtgärd har tolkats. Anta att en aktivitet som innehåller en återförsöksprincip anropar en annan aktivitet som också innehåller en återförsöksprincip. Det här extra återförsökslagret kan lägga till långa fördröjningar i bearbetningen. Det kan vara bättre att konfigurera aktiviteten på lägre nivå så att den misslyckas snabbt och rapporterar felorsaken till aktiviteten bakom anropet. Aktiviteten på högre nivå kan sedan hantera felet utifrån sin egen princip.

Det är viktigt att logga alla anslutningsfel som orsakar ett återförsök, så att underliggande problem med program, tjänster eller resurser kan identifieras.

Undersök de fel som är mest sannolika för en tjänst eller en resurs för att ta reda på om de förväntas vara långvariga eller permanenta. Om så är fallet är det bättre att felet hanteras som ett undantag. Programmet kan rapportera eller logga undantaget, och sedan fortsätta genom att anropa en annan tjänst (om en sådan finns) eller genom att tillhandahålla försämrad funktionalitet. Mer information om att identifiera och hantera långvariga fel finns på sidan om kretsbrytarmönstret.

När du ska använda det här mönstret

Använd det här mönstret om det uppstår tillfälliga fel i ett program när det interagerar med en fjärrtjänst eller ansluter till en fjärresurs. Dessa fel förväntas vara kortvariga och när ett återförsök görs för en begäran som misslyckats kan det ge ett lyckat resultat.

Det här mönstret är kanske inte användbart om:

  • Ett fel troligtvis är långvarigt, eftersom detta kan påverka svarstiden för ett program. Programmet förlorar tid och resurser på att försöka upprepa en begäran som troligen kommer att misslyckas.
  • Misslyckanden som inte beror på tillfälliga fel hanteras, till exempel interna undantag orsakade av fel i affärslogiken i ett program.
  • Det är ett alternativ till att åtgärda skalningsproblem i ett system. Om det ofta uppstår upptaget-fel i ett program, brukar det vara en indikering på att tjänsten eller resursen ifråga bör skalas upp.

Exempel

Det här exemplet i C# visar en implementering av återförsöksmönstret. Metoden OperationWithBasicRetryAsync, som visas nedan, anropar en extern tjänst asynkront genom metoden TransientOperationAsync. TransientOperationAsync-metodens uppgifter gäller särskilt för den tjänsten och har utelämnats från exempelkoden.

private int retryCount = 3;
private readonly TimeSpan delay = TimeSpan.FromSeconds(5);

public async Task OperationWithBasicRetryAsync()
{
  int currentRetry = 0;

  for (;;)
  {
    try
    {
      // Call external service.
      await TransientOperationAsync();

      // Return or break.
      break;
    }
    catch (Exception ex)
    {
      Trace.TraceError("Operation Exception");

      currentRetry++;

      // Check if the exception thrown was a transient exception
      // based on the logic in the error detection strategy.
      // Determine whether to retry the operation, as well as how
      // long to wait, based on the retry strategy.
      if (currentRetry > this.retryCount || !IsTransient(ex))
      {
        // If this isn't a transient error or we shouldn't retry,
        // rethrow the exception.
        throw;
      }
    }

    // Wait to retry the operation.
    // Consider calculating an exponential delay here and
    // using a strategy best suited for the operation and fault.
    await Task.Delay(delay);
  }
}

// Async method that wraps a call to a remote service (details not shown).
private async Task TransientOperationAsync()
{
  ...
}

Instruktionen som anropar den här metoden finns i ett try-/catch-block, inkapslat i en For-loop. For-loopen avslutas om anropet till metoden TransientOperationAsync lyckas utan att ett undantag utlöses. Om metoden TransientOperationAsync misslyckas undersöker catch-blocket orsaken till felet. Om det anses vara ett tillfälligt fel väntar koden en kort stund innan åtgärden försöks igen.

For-loopen spårar också antalet försök för åtgärden och om koden misslyckas tre gånger antas undantaget vara långvarigt. Om undantaget inte är tillfälligt, eller om det är långvarigt, utlöser catch-hanteraren ett undantag. Det här undantaget lämnar For-loopen och bör fångas upp av koden som anropar metoden OperationWithBasicRetryAsync.

Metoden IsTransient, som visas nedan, kontrollerar om det finns en specifik uppsättning undantag som är relevanta för miljön där koden körs. Definitionen av ett tillfälligt undantag varierar beroende på de resurser som används och miljön där åtgärden utförs.

private bool IsTransient(Exception ex)
{
  // Determine if the exception is transient.
  // In some cases this is as simple as checking the exception type, in other
  // cases it might be necessary to inspect other properties of the exception.
  if (ex is OperationTransientException)
    return true;

  var webException = ex as WebException;
  if (webException != null)
  {
    // If the web exception contains one of the following status values
    // it might be transient.
    return new[] {WebExceptionStatus.ConnectionClosed,
                  WebExceptionStatus.Timeout,
                  WebExceptionStatus.RequestCanceled }.
            Contains(webException.Status);
  }

  // Additional exception checking logic goes here.
  return false;
}

Nästa steg

  • Kretsbrytarmönster. Om ett fel förväntas vara långvarigt kan det vara lämpligare att implementera kretsbrytarmönstret. Genom att kombinera återförsöks- och kretsbrytarmönstren får du en heltäckande metod för att hantera fel.

  • När du bearbetar kommandon som ändrar affärsdata bör du vara medveten om att återförsök kan resultera i att åtgärden utförs två gånger, vilket kan vara problematiskt om åtgärden är något som att debitera en kunds kreditkort. Med hjälp av Idempotence-mönstret som beskrivs i det här blogginlägget kan du hantera dessa situationer.