CircuitBreaker

det är vanligt att mjukvarusystem gör fjärranrop till programvara som körs i olika processer, förmodligen på olika maskiner över ett nätverk. En av de stora skillnaderna mellan samtal i minnet och fjärranrop är att fjärranrop kan misslyckas eller hänga utan svar tills någon tidsgräns har uppnåtts. Vad som är värre om du har många uppringare på en leverantör som inte svarar, kan du få slut på kritiska resurser som leder till kaskadfel över flera system. I sin utmärkta bokrelease It populariserade Michael Nygard kretsbrytaren för att förhindra denna typ av katastrofal kaskad.

grundtanken bakom brytaren är mycket enkel. Du sätter in ett skyddat funktionsanrop i ett brytarobjekt som övervakar för fel. När misslyckandena når en viss tröskel, går brytaren och alla ytterligare samtal till brytaren återvänder med ett fel, utan att det skyddade samtalet alls görs. Vanligtvis vill du också ha någon form av monitorvarning om strömbrytaren går ut.

här är ett enkelt exempel på detta beteende i Ruby, som skyddar mot timeout.

jag ställer in brytaren med ett block (Lambda) som är det skyddade samtalet.

cb = CircuitBreaker.new {|arg| @supplier.func arg}

brytaren lagrar blocket, initierar olika parametrar (för tröskelvärden, timeouts och övervakning) och återställer brytaren till dess stängda tillstånd.

klass kretsbrytare…

 attr_accessor :invocation_timeout, :failure_threshold, :monitor def initialize &block @circuit = block @invocation_timeout = 0.01 @failure_threshold = 5 @monitor = acquire_monitor reset end

anropa brytaren anropar det underliggande blocket om kretsen är stängd, men returnerar ett fel om det är öppet

# client code aCircuitBreaker.call(5)

klass kretsbrytare…

 def call args case state when :closed begin do_call args rescue Timeout::Error record_failure raise $! end when :open then raise CircuitBreaker::Open else raise "Unreachable Code" end end def do_call args result = Timeout::timeout(@invocation_timeout) do @circuit.call args end reset return result end

om vi får en timeout, ökar vi felräknaren, framgångsrika samtal återställer den till noll.

klass kretsbrytare…

 def record_failure @failure_count += 1 @monitor.alert(:open_circuit) if :open == state end def reset @failure_count = 0 @monitor.alert :reset_circuit end

jag bestämmer brytarens tillstånd och jämför felet med tröskeln

klasskretsbrytare…

 def state (@failure_count >= @failure_threshold) ? :open : :closed end

denna enkla brytare undviker att göra det skyddade samtalet när kretsen är öppen, men skulle behöva ett externt ingripande för att återställa det när sakerna är bra igen. Detta är ett rimligt tillvägagångssätt med elektriska brytare i byggnader, men för programvarubrytare kan vi få brytaren själv att upptäcka om de underliggande samtalen fungerar igen. Vi kan implementera detta självåterställningsbeteende genom att prova det skyddade samtalet igen efter ett lämpligt intervall och återställa brytaren om det skulle lyckas.

att skapa denna typ av brytare innebär att man lägger till ett tröskelvärde för att försöka återställa och ställa in en variabel för att hålla tiden för det senaste felet.

klass ResetCircuitBreaker…

 def initialize &block @circuit = block @invocation_timeout = 0.01 @failure_threshold = 5 @monitor = BreakerMonitor.new @reset_timeout = 0.1 reset end def reset @failure_count = 0 @last_failure_time = nil @monitor.alert :reset_circuit end

det finns nu ett tredje tillstånd närvarande – halv öppet – vilket betyder att kretsen är redo att ringa ett riktigt samtal som försök för att se om problemet är löst.

klass ResetCircuitBreaker…

 def state case when (@failure_count >= @failure_threshold) && (Time.now - @last_failure_time) > @reset_timeout :half_open when (@failure_count >= @failure_threshold) :open else :closed end end

ombedd att ringa i halvöppet tillstånd resulterar i ett provsamtal, vilket antingen återställer brytaren om det lyckas eller startar om timeout om inte.

klass ResetCircuitBreaker…

 def call args case state when :closed, :half_open begin do_call args rescue Timeout::Error record_failure raise $! end when :open raise CircuitBreaker::Open else raise "Unreachable" end end def record_failure @failure_count += 1 @last_failure_time = Time.now @monitor.alert(:open_circuit) if :open == state end

detta exempel är en enkel förklarande en, i praktiken brytare ger en bra bit fler funktioner och parametrering. Ofta kommer de att skydda mot en rad fel som skyddade samtal kan höja, till exempel nätverksanslutningsfel. Inte alla fel bör resa kretsen, vissa bör återspegla normala fel och hanteras som en del av vanlig logik.

med mycket trafik kan du få problem med många samtal som bara väntar på den första timeout. Eftersom fjärrsamtal ofta är långsamma är det ofta bra att sätta varje samtal på en annan tråd med en framtid eller lova att hantera resultaten när de kommer tillbaka. Genom att dra dessa trådar från en trådpool kan du ordna att kretsen går sönder när trådpoolen är uttömd.

exemplet visar ett enkelt sätt att resa brytaren — en räkning som återställs vid ett lyckat samtal. Ett mer sofistikerat tillvägagångssätt kan titta på frekvensen av fel, snubbla när du får, säg, en 50% felfrekvens. Du kan också ha olika tröskelvärden för olika fel, till exempel en tröskel på 10 för timeout men 3 för anslutningsfel.

exemplet jag har visat är en brytare för synkrona samtal, men brytare är också användbara för asynkron kommunikation. En vanlig teknik här är att lägga alla förfrågningar i en kö, som leverantören förbrukar med sin hastighet – en användbar teknik för att undvika överbelastning av servrar. I detta fall bryts kretsen när kön fylls.

på egen hand bidrar brytare till att minska resurser som är bundna i operationer som sannolikt kommer att misslyckas. Du undviker att vänta på timeout för klienten, och en trasig krets undviker att lägga belastning på en kämpande server. Jag pratar här om fjärranrop, som är ett vanligt fall för brytare, men de kan användas i alla situationer där du vill skydda delar av ett system från fel i andra delar.

brytare är en värdefull plats för övervakning. Varje förändring i brytare tillstånd bör loggas och brytare bör avslöja uppgifter om deras tillstånd för djupare övervakning. Breaker beteende är ofta en bra källa till varningar om djupare problem i miljön. Driftspersonal ska kunna resa eller återställa brytare.

brytare på egen hand är värdefulla, men kunder som använder dem måste reagera på brytfel. Som med alla fjärranrop måste du överväga vad du ska göra vid fel. Misslyckas den operation du utför, eller finns det lösningar du kan göra? Ett kreditkort tillstånd kan läggas på en kö för att ta itu med senare, underlåtenhet att få vissa data kan mildras genom att visa några inaktuella data som är tillräckligt bra för att visa.

Vidare läsning

netflix tech blog innehåller mycket användbar information om att förbättra tillförlitligheten hos system med många tjänster. Deras beroende kommando talar om att använda Brytare och en tråd pool gräns.

Netflix har öppen källkod Hystrix, ett sofistikerat verktyg för att hantera latens och feltolerans för distribuerade system. Den innehåller en implementering av brytaren mönstret med tråden pool gräns

det finns andra öppen källkod implementeringar av brytaren mönstret i Ruby, Java, Grails Plugin, C#, AspectJ, och Scala

bekräftelser

Pavel Shpak upptäckte och rapporterade ett fel i exempelkoden

Lämna ett svar

Din e-postadress kommer inte publiceras.