Kretsbrytarmönster

Azure

Hantera fel som kan ta varierande lång tid för återhämtning vid anslutning till en fjärrtjänst eller fjärresurs. Det här kan förbättra ett programs stabilitet och återhämtning.

Kontext och problem

I en distribuerad miljö kan anrop till fjärresurser och -tjänster misslyckas på grund av tillfälliga problem, till exempel långsam nätverksanslutning, timeouter eller för att resurserna är överallokerade eller tillfälligt otillgängliga. Vanligtvis rättar felen till sig själva efter en kort tid och ett stabilt molnprogram ska vara förberett på att hantera dem genom att använda en strategi som Återförsöksmönster.

Det kan dock också finnas situationer då fel beror på oväntade händelser, och det kan ta mycket längre tid att åtgärda. Felen kan variera i allvarlighetsgrad, från en partiell förlust av anslutning till fullständigt fel i en tjänst. I sådana fall kan det vara meningslöst för ett program att upprepade gånger försöka utföra en åtgärd som förmodligen inte kommer att lyckas. Istället bör programmet snabbt acceptera att åtgärden misslyckats och hantera felet på lämpligt vis.

Om en tjänst är väldigt trafikerad kan fel i en del av systemet dessutom leda till flera fel följda på varandra. En åtgärd som startar en tjänst kan exempelvis konfigureras till att implementera en timeout och svara med ett felmeddelande om tjänsten inte svarar under den här tidsperioden. Den här strategin kan dock orsaka många samtida begäranden om att samma åtgärd ska blockeras tills tidsgränsen upphört gälla. Dessa blockerade begäranden kan använda kritiska systemresurser som minne, trådar, databasanslutningar och så vidare. Därför kan de här resurserna bli uttömda och orsaka fel i andra eventuellt orelaterade delar i systemet som behöver använda samma resurser. I sådana fall är det att föredra att åtgärden misslyckas omgående och endast försöker anropa tjänsten om det är troligt att den lyckas. Observera att en kortare tidsgräns kan bidra till att lösa det här problemet, men tidsgränsen bör inte vara så kort att åtgärden oftast misslyckas, även om begärandet av tjänsten till slut lyckas.

Lösning

Mönstret Kretsbrytare, känt från Michael Nygards bok, Release It!, kan hindra ett program från att upprepade gånger försöka genomföra en åtgärd som troligtvis kommer att misslyckas. Gör det möjligt att fortsätta utan att vänta på att felet ska åtgärdas eller slösa CPU-cykler medan den avgör att felet är långvarigt. Mönstret Kretsbrytare gör det också möjligt för ett program att identifiera huruvida felet har åtgärdats. Om problemet verkar ha åtgärdats kan programmet försöka anropa åtgärden.

Syftet med mönstret Kretsbrytare är olikt syftet med Återförsöksmönster. Återförsöksmönster igen gör det möjligt för ett program att försöka utföra en åtgärd igen med en förväntan om att den ska lyckas. Mönstret Kretsbrytare förhindrar att ett program försöker utföra en åtgärd som troligen kommer att misslyckas. Ett program kan kombinera de två mönstren med hjälp av Återförsöksmönster och anropa en åtgärd via en kretsbrytare. Logiken för att försöka igen bör vara känslig för undantag som returneras av kretsbrytaren och överge försök om kretsbrytaren indikerar att felet inte är tillfälligt.

En kretsbrytare fungerar som proxy för åtgärder som kan misslyckas. Proxyn ska övervaka antalet senaste fel som har inträffat och använda informationen till att bestämma huruvida åtgärden ska fortsätta eller helt enkelt returnera ett undantag omedelbart.

Proxyn kan implementeras som en tillståndsdator med följande tillstånd som efterliknar funktionerna i en elektriska kretsbrytare:

  • Stängd: ett begärande från programmet dirigeras till åtgärden. Proxyn upprätthåller en uppräkning av antalet senaste fel och om anropet till åtgärden misslyckas ökar proxyn det här antalet. Om antalet senaste fel överskrider ett angivet tröskelvärde inom en viss tidsperiod placeras proxyn i Öppet tillstånd. Nu startar proxyn en timeout-timer och när timern stannar placeras proxyn i Halvöppet tillstånd.

    Syftet med timeout-timern är att ge systemet tid att åtgärda problemet som orsakade felet, innan programmet försöker utföra åtgärden igen.

  • Öppna: ett begärande från programmet misslyckas omedelbart och ett undantag returneras till programmet.

  • Halvöppet: ett begränsat antal begäranden från programmet tillåts passera och anropa åtgärden. Om de här begärandena lyckas förutsätts det att det tidigare felet har åtgärdats och kretsbrytaren växlar till Stängt tillstånd (felberäkningen återställs). Om någon begäran misslyckas förutsätter kretsbrytaren att felet fortfarande finns så att det återgår till öppet tillstånd och startar om timeout-timern för att ge systemet ytterligare en tidsperiod att återställa från felet.

    Halvöppet tillstånd är användbart för att förhindra att en tjänst som håller på att återställas plötsligt översvämmas med begäranden. När en tjänst håller på att återställas kanske den har stöd för ett begränsat antal begäranden tills återställningen är slutförd, men medan återställningen pågår kan ett arbetsflöde göra så att tjänsten pausas eller misslyckas igen.

Kretsbrytarens tillstånd

På bilden visas räknaren för misslyckande som används av Stängt tillstånd och är tidsbaserad. Den återställs automatiskt med jämna mellanrum. På så vis hindras kretsbrytaren från att försättas i Öppet tillstånd, om tillfälliga fel inträffar. Tröskelvärde för fel som ställer kretsbrytaren i Öppet tillstånd nås endast när ett angivet antal fel har inträffat under en angiven tidsperiod. Räknaren som används av Halvöppet tillstånd registrerar antalet lyckade försök att anropa åtgärden. Kretsbrytaren återgår till Stängt tillstånd efter att ett angivet antal på varandra följande åtgärdsanrop har lyckats. Om alla anrop misslyckas försätts kretsbrytaren omedelbart i Öppet tillstånd och räknaren för lyckade anrop återställs nästa gång den försätts i Halvöppet tillstånd.

Återställning av systemet hanteras externt, möjligtvis genom att återställa eller starta om en misslyckad komponent eller reparera en nätverksanslutning.

Mönstret Kretsbrytare ger stabilitet medan systemet återställer från ett fel och minimerar påverkan på prestandan. Det kan bidra till att bibehålla systemets svarstid genom att snabbt avvisa ett begärande om en åtgärd som troligen kommer att misslyckas, i stället för att vänta tills tidsgränsen för åtgärden nås eller aldrig returneras. Om kretsbrytaren utlöser en händelse när status ändras kan den här informationen användas för att övervaka hälsotillståndet för en del av systemet som skyddas av kretsbrytaren eller varna en administratör när en kretsbrytare sätts i Öppet tillstånd.

Mönstret är anpassningsbart och kan anpassas enligt typen av det möjliga felet. Du kan till exempel använda en ökande timeout-timer för en kretsbrytare. Du kan till en början placera kretsbrytaren i Öppet tillstånd under några sekunder och sedan, om misslyckandet inte åtgärdas, öka till några minuter, osv. I vissa fall kan det vara användbart att återgå till ett standardvärde som är av betydelse för programmet istället för att Öppet tillstånd returnerar fel och utlöser ett undantag.

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:

Undantagshantering. Ett program som anropar en åtgärd via en kretsbrytare måste förberedas för att hantera undantagen som aktiveras om åtgärden inte är tillgänglig. Hanteringen av undantag är specifikt för programmet. Till exempel kan ett program tillfälligt försämra dess funktioner, anropa en annan åtgärd i ett försök att genomföra samma uppgift eller hämta samma data, eller rapportera undantag för användaren och be dem att försök igen senare.

Typer av undantag. Ett begärande kan misslyckas av flera orsaker. Vissa kan tyda på en allvarligare typ av fel än andra. Ett begärande kan till exempel misslyckas på grund av att fjärrtjänsten har kraschat och det kan ta flera minuter att återställa den eller på grund av en tidsgräns, då tjänsten är tillfälligt överbelastad. En kretsbrytare kan eventuellt undersöka undantagstyperna som inträffar och justera dess strategi beroende på undantagens egenskaper. Att sätta kretsbrytaren i Öppet tillstånd kan exempelvis kräva ett större antal timeout-undantag jämfört med antalet misslyckanden till följd av att tjänsten är helt otillgänglig.

Loggning. En kretsbrytare ska logga alla misslyckade begäranden (och eventuellt lyckade begäranden), så att en administratör kan övervaka åtgärdens hälsotillstånd.

Återställning. Du bör konfigurera kretsbrytaren så att den matchar mönstret för trolig återställning för åtgärden som skyddas. Om kretsbrytaren exempelvis förblir i Öppet tillstånd under en lång period kan undantag utlösas även om anledningen till felet har åtgärdats. På samma sätt kan en kretsbrytare fluktuera och minska programmets svarstid om den växlar från Öppet tillstånd till Halvöppet tillstånd för snabbt.

Testa misslyckade åtgärder. I Öppet tillstånd kan en kretsbrytare i stället pinga fjärrtjänsten eller resursen regelbundet, för att kontrollera om det finns bli tillgänglig igen, i stället för att avgöra när du vill växla till Halvöppet tillstånd med hjälp av en timer. Den här pingen kan fungera som ett försök att anropa en åtgärd som tidigare misslyckats eller så kan den användas som en särskild åtgärd som tillhandahålls av fjärrtjänsten för testning av tjänstens hälsotillstånd, så som beskrivs av Health Endpoint Monitoring pattern (Övervakningsmönster för slutpunktens tillstånd).

Manuell åsidosättning. I ett system där återställningstiden för en misslyckad åtgärd varierar mycket kan det vara användbart att skapa ett manuellt återställningsalternativ som gör det möjligt för en administratör att stänga en kretsbrytare (och återställa räknaren för misslyckande). På samma sätt kan en administratör tvinga en kretsbrytare till Öppet tillstånd (och starta om timeout-timern) om åtgärden som skyddas av kretsbrytaren för tillfället inte är tillgänglig.

Samtidighet. Samma kretsbrytare kan få åtkomst till ett stort antal samtida instanser av ett program. Implementeringen bör inte blockera samtida begäranden eller tillföra ytterligare overhead för varje anrop till en åtgärd.

Resursdifferentiering. Var försiktig när du använder en enda kretsbrytare för en typ av resurs om det kan finnas flera underliggande oberoende leverantörer. I exempelvis ett datalager som innehåller flera shards kan en shard vara fullt åtkomlig medan andra upplever ett tillfälligt fel. Om felsvaren i de här scenariona slås samman kan ett program försöka komma åt vissa shards, även när ett fel är högst troligt, medan åtkomst till andra shards kan vara blockerade trots att det är troligt att det skulle lyckas.

Snabbare kretsbrytning. Ibland kan ett felsvar innehålla tillräckligt med information för kretsbrytaren att utlösas omedelbart och förbli utlöst under en minimiperiod. Felsvar från en delad resurs som är överbelastad kan till exempel indikera att det inte rekommenderas att omedelbart försöka igen och att programmet stället ska försök igen om några minuter.

Kommentar

En tjänst kan returnera HTTP 429 (för många begäranden) om klienten begränsas eller HTTP 503 (tjänsten är otillgänglig) om tjänsten för tillfället inte är tillgänglig. Svaret kan innehålla ytterligare information, till exempel fördröjningens förväntade varaktighet.

Spela upp misslyckade begäranden igen. I Öppet tillstånd, istället för att helt enkelt misslyckas snabbt, kan en kretsbrytare också registrera information för varje begärande till en journal och ordna så att begärandena spelas upp en gång till när fjärresurser eller -tjänster blir tillgängliga.

Olämpliga timeouter på externa tjänster. En kretsbrytare kanske inte kan skydda program fullt ut från åtgärder som misslyckas i externa tjänster som konfigurerats med långa timeout-perioder. Om en timeout är för lång kanske en tråd som kör en kretsbrytare blockerats under en längre period innan kretsbrytaren indikerar att åtgärden misslyckats. Under den här tiden kan många andra programinstanser också försöka anropa tjänsten via kretsbrytaren och foga samman ett betydande antal trådar innan de misslyckas.

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

Använd det här mönstret:

  • Om du vill förhindra ett program från att försöka anropa en fjärrtjänst eller från att få åtkomst till en delad resurs om det är mycket troligt att åtgärden kommer att misslyckas.

Det här mönstret rekommenderas inte:

  • För hantering av åtkomst till lokala privata resurser i ett program, till exempel InMemory-datastruktur. I den här miljön skulle en kretsbrytare lägga till overhead i systemet.
  • Som ett substitut för att hantera undantag i ditt programs affärslogik.

Design av arbetsbelastning

En arkitekt bör utvärdera hur kretsbrytarmönstret kan användas i arbetsbelastningens design för att uppfylla de mål och principer som beskrivs i grundpelarna i Azure Well-Architected Framework. Till exempel:

Grundpelare Så här stöder det här mönstret pelarmål
Beslut om tillförlitlighetsdesign hjälper din arbetsbelastning att bli motståndskraftig mot fel och se till att den återställs till ett fullt fungerande tillstånd när ett fel inträffar. Det här mönstret förhindrar överlagring av ett felberoende. Du kan också använda det här mönstret för att utlösa en graciös försämring i arbetsbelastningen. Kretsbrytare är ofta kopplade till automatisk återställning för att ge både självbevarelsedrift och självåterställning.

- RE:03 Analys av felläge
- RE:07 Tillfälliga fel
- RE:07 Självbevarande
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. Det här mönstret undviker metoden för återförsök på fel, vilket kan leda till överdriven resursanvändning under beroendeåterställning och kan även överbelasta prestanda för ett beroende som försöker återställa.

- PE:07 Kod och infrastruktur
- PE:11 Live-problem svar

Som med alla designbeslut bör du överväga eventuella kompromisser mot målen för de andra pelarna som kan införas med det här mönstret.

Exempel

I ett webbaserat program är flera av sidorna ifyllda med data som hämtats från externa tjänster. Om system implementerar minimal cachelagring orsakar de flesta träffar på den här sidan en tur och retur till tjänsten. Anslutningar mellan det webbaserade programmet och tjänsten kan konfigureras med en timeout-period (vanligtvis 60 sekunder) och om tjänsten inte svarar under den här tiden förutsätter varje webbsidas logik att tjänsten är otillgänglig och utlöser ett undantag.

Om tjänsten däremot misslyckas och systemet är mycket trafikerat kan användare behöva vänta i upp till 60 sekunder innan undantaget inträffar. Så småningom kan resurser som minne, anslutningar och trådar bli uttömda och hindra andra användare från att ansluta till systemet, trots att de inte använder sidan som hämtar data från tjänsten.

Med hjälp av skalning av systemet, genom att lägga till ytterligare webbservrar och implementera belastningsutjämning, kan uttömning av resurserna fördröjas. Det här åtgärdar dock inga problem, eftersom användarbegäranden inte svarar och resurserna fortfarande kan ta slut på alla webbservrar så småningom.

Att omsluta logiken som ansluter tjänsten och hämtar data i kretsbrytaren kan bidra till att åtgärda problemet och hantera tjänstens fel med mer finess. Användarbegäranden kommer fortfarande misslyckas men de misslyckas snabbare och resurserna blockeras inte.

Klassen CircuitBreaker bibehåller lägesinformation om en kretsbrytare i ett objekt som implementerar gränssnittet ICircuitBreakerStateStore som visas i följande kod.

interface ICircuitBreakerStateStore
{
  CircuitBreakerStateEnum State { get; }

  Exception LastException { get; }

  DateTime LastStateChangedDateUtc { get; }

  void Trip(Exception ex);

  void Reset();

  void HalfOpen();

  bool IsClosed { get; }
}

Egenskapen State indikerar kretsbrytarens aktuella tillstånd som antingen är Öppet, Halvöppet eller Stängt så som definieras av uppräkningen CircuitBreakerStateEnum. Egenskapen IsClosed bör vara sann om kretsbrytaren är stängd och falsk om den är öppen eller halvöppen. Metoden Trip växlar kretsbrytarens tillstånd till öppet tillstånd och registrerar undantaget som orsakade det ändrade tillståndet, tillsammans med datum och tid för när undantaget inträffade. Egenskaperna LastException och LastStateChangedDateUtc returnerar den här informationen. Metoden Reset stänger kretsbrytaren och metoden HalfOpen sätter kretsbrytaren i öppet tillstånd.

Klassen InMemoryCircuitBreakerStateStore i exemplet innehåller en implementering av ICircuitBreakerStateStore-gränssnittet. Klassen CircuitBreaker skapar en instans, så att klassen behåller kretsbrytarens tillstånd.

Metoden ExecuteAction i klassen CircuitBreaker omsluter en åtgärd, specificerad som en Action-delegat. Om kretsbrytaren stängs anropar ExecuteActionAction-delegaten. Om åtgärden misslyckas anropas TrackException av en undantagshanterare som anger kretsbrytarens tillstånd som öppet. Följande kodexempel understryker det här flödet.

public class CircuitBreaker
{
  private readonly ICircuitBreakerStateStore stateStore =
    CircuitBreakerStateStoreFactory.GetCircuitBreakerStateStore();

  private readonly object halfOpenSyncObject = new object ();
  ...
  public bool IsClosed { get { return stateStore.IsClosed; } }

  public bool IsOpen { get { return !IsClosed; } }

  public void ExecuteAction(Action action)
  {
    ...
    if (IsOpen)
    {
      // The circuit breaker is Open.
      ... (see code sample below for details)
    }

    // The circuit breaker is Closed, execute the action.
    try
    {
      action();
    }
    catch (Exception ex)
    {
      // If an exception still occurs here, simply
      // retrip the breaker immediately.
      this.TrackException(ex);

      // Throw the exception so that the caller can tell
      // the type of exception that was thrown.
      throw;
    }
  }

  private void TrackException(Exception ex)
  {
    // For simplicity in this example, open the circuit breaker on the first exception.
    // In reality this would be more complex. A certain type of exception, such as one
    // that indicates a service is offline, might trip the circuit breaker immediately.
    // Alternatively it might count exceptions locally or across multiple instances and
    // use this value over time, or the exception/success ratio based on the exception
    // types, to open the circuit breaker.
    this.stateStore.Trip(ex);
  }
}

Följande exempel visar koden (utelämnas från föregående exempel) som körts om kretsbrytaren inte stängts. Det kontrollerar om kretsbrytaren har varit öppen under längre tid än den tid som angivits av det lokala OpenToHalfOpenWaitTime-fältet i CircuitBreaker-klassen. I sådana fall sätter ExecuteAction-metoden kretsbrytaren i halvöppet tillstånd och försöker sedan utföra åtgärden som specificerats av Action-delegaten.

Om åtgärden lyckas återgår kretsbrytaren till stängt tillstånd. Om åtgärden misslyckas skickas den tillbaka till öppet tillstånd och tiden som undantaget inträffade uppdateras, så att kretsbrytaren inväntar ännu en stund innan den utför åtgärden igen.

Om kretsbrytaren bara varit öppen en kort tid, mindre än värdet OpenToHalfOpenWaitTime utlöser metoden ExecuteAction bara ett CircuitBreakerOpenException-undantag och returnerar felet som orsakade att kretsbrytaren övergick till öppet tillstånd.

Dessutom använder den ett lås som hindrar kretsbrytaren från att försöka genomföra samtida anrop till åtgärden när den är halvöppen. Ett samtida försök att anropa åtgärden hanteras som om kretsbrytaren var öppen och misslyckas med ett undantag som beskrivs senare.

    ...
    if (IsOpen)
    {
      // The circuit breaker is Open. Check if the Open timeout has expired.
      // If it has, set the state to HalfOpen. Another approach might be to
      // check for the HalfOpen state that had be set by some other operation.
      if (stateStore.LastStateChangedDateUtc + OpenToHalfOpenWaitTime < DateTime.UtcNow)
      {
        // The Open timeout has expired. Allow one operation to execute. Note that, in
        // this example, the circuit breaker is set to HalfOpen after being
        // in the Open state for some period of time. An alternative would be to set
        // this using some other approach such as a timer, test method, manually, and
        // so on, and check the state here to determine how to handle execution
        // of the action.
        // Limit the number of threads to be executed when the breaker is HalfOpen.
        // An alternative would be to use a more complex approach to determine which
        // threads or how many are allowed to execute, or to execute a simple test
        // method instead.
        bool lockTaken = false;
        try
        {
          Monitor.TryEnter(halfOpenSyncObject, ref lockTaken);
          if (lockTaken)
          {
            // Set the circuit breaker state to HalfOpen.
            stateStore.HalfOpen();

            // Attempt the operation.
            action();

            // If this action succeeds, reset the state and allow other operations.
            // In reality, instead of immediately returning to the Closed state, a counter
            // here would record the number of successful operations and return the
            // circuit breaker to the Closed state only after a specified number succeed.
            this.stateStore.Reset();
            return;
          }
        }
        catch (Exception ex)
        {
          // If there's still an exception, trip the breaker again immediately.
          this.stateStore.Trip(ex);

          // Throw the exception so that the caller knows which exception occurred.
          throw;
        }
        finally
        {
          if (lockTaken)
          {
            Monitor.Exit(halfOpenSyncObject);
          }
        }
      }
      // The Open timeout hasn't yet expired. Throw a CircuitBreakerOpen exception to
      // inform the caller that the call was not actually attempted,
      // and return the most recent exception received.
      throw new CircuitBreakerOpenException(stateStore.LastException);
    }
    ...

Om du vill skydda en åtgärd med ett CircuitBreaker-objekt skapar ett program en instans av CircuitBreaker-klassen, anropar ExecuteAction-metoden och anger åtgärden som ska utföras som parameter. Programmet bör vara förberett på att fånga upp CircuitBreakerOpenException-undantaget om åtgärden misslyckas, eftersom kretsbrytaren är öppen. Följande kod visar ett exempel:

var breaker = new CircuitBreaker();

try
{
  breaker.ExecuteAction(() =>
  {
    // Operation protected by the circuit breaker.
    ...
  });
}
catch (CircuitBreakerOpenException ex)
{
  // Perform some different action when the breaker is open.
  // Last exception details are in the inner exception.
  ...
}
catch (Exception ex)
{
  ...
}

Följande mönster kan vara användbara när du implementerar det här mönstret:

  • Tillförlitligt webbappmönster visar hur du tillämpar kretsbrytarmönstret på webbprogram som konvergerar i molnet.

  • Återförsöksmönster. Beskriver hur ett program kan hantera tillfälliga fel som är väntade när det försöker ansluta till en tjänst eller nätverksresurs genom att transparent försöka utföra en åtgärd som tidigare misslyckats igen.

  • Hälsoslutpunktsövervakningsmönster. En kretsbrytare kan eventuellt testa en tjänsts hälsa genom att skicka ett begärande till en slutpunkt som exponeras av tjänsten. Tjänsten ska returnera information med dess status.