CircuitBreaker

det er vanlig for programvaresystemer å foreta eksterne samtaler til programvare som kjører i forskjellige prosesser, sannsynligvis på forskjellige maskiner over et nettverk. En av de store forskjellene mellom in-memory samtaler og eksterne samtaler er at eksterne samtaler kan mislykkes, eller henge uten svar til noen timeout grense er nådd. Hva er verre hvis du har mange innringere på en leverandør som ikke svarer, kan du gå tom for kritiske ressurser som fører til cascading feil på tvers av flere systemer. I sin utmerkede bokutgivelse It populariserte Michael Nygard Kretsbrytermønsteret for å forhindre denne typen katastrofale kaskade.

den grunnleggende ideen bak bryteren er veldig enkel. Du pakker inn et beskyttet funksjonskall i et kretsbryterobjekt, som overvåker feil. Når feilene når en viss terskel, går bryteren, og alle videre samtaler til bryteren returnerer med en feil, uten at det beskyttede anropet blir gjort i det hele tatt. Vanligvis vil du også ha en slags skjermvarsel hvis bryteren går.

Her er et enkelt eksempel på Denne oppførselen I Ruby, som beskytter mot tidsavbrudd.

jeg satte opp bryteren med en blokk (Lambda) som er det beskyttede anropet.

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

bryteren lagrer blokken, initialiserer ulike parametere (for terskler, tidsavbrudd og overvåking) og tilbakestiller bryteren i lukket tilstand.

klasse Kretsbryter…

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

Ringer strømbryteren vil ringe den underliggende blokken hvis kretsen er lukket, men returnere en feil hvis den er åpen

# client code aCircuitBreaker.call(5)

klasse Kretsbryter…

 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

skal vi få en timeout, øker vi feiltelleren, vellykkede samtaler tilbakestiller den tilbake til null.

klasse Kretsbryter…

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

jeg bestemmer tilstanden til bryteren som sammenligner feiltellingen til terskelen

klasse CircuitBreaker…

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

denne enkle bryteren unngår å gjøre det beskyttede anropet når kretsen er åpen, men vil trenge en ekstern inngrep for å tilbakestille den når ting er bra igjen. Dette er en rimelig tilnærming med elektriske effektbrytere i bygninger, men for programvare effektbrytere kan vi ha bryteren selv oppdage hvis de underliggende samtalene fungerer igjen. Vi kan implementere denne selvtilbakestillingsadferden ved å prøve det beskyttede anropet igjen etter et passende intervall, og tilbakestille bryteren hvis den lykkes.

Å Lage denne typen bryter betyr å legge til en terskel for å prøve tilbakestillingen og sette opp en variabel for å holde tiden for den siste feilen.

klasse 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 er nå en tredje tilstand til stede – halv åpen-noe som betyr at kretsen er klar til å ringe som prøve for å se om problemet er løst.

klasse 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

Hvis Du Blir bedt om å ringe i halvåpent tilstand, resulterer det i en prøvesamtale, som enten tilbakestiller bryteren hvis den lykkes, eller starter tidsavbruddet på nytt hvis ikke.

klasse 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

dette eksemplet er en enkel forklarende, i praksis gir strømbrytere en god bit flere funksjoner og parameterisering. Ofte vil de beskytte mot en rekke feil som beskyttet samtale kan øke, for eksempel nettverkstilkoblingsfeil. Ikke alle feil bør reise kretsen, noen bør gjenspeile normale feil og behandles som en del av vanlig logikk.

med mye trafikk kan du få problemer med mange samtaler som bare venter på den første timeout. Siden eksterne samtaler ofte er sakte, er det ofte en god ide å sette hver samtale på en annen tråd ved hjelp av en fremtid eller løfte om å håndtere resultatene når de kommer tilbake. Ved å tegne disse trådene fra en tråd basseng, kan du ordne for kretsen å bryte når tråden bassenget er oppbrukt.

eksemplet viser en enkel måte å utløse bryteren på – et antall som tilbakestilles på en vellykket samtale. En mer sofistikert tilnærming kan se på frekvensen av feil, tripping når du får en 50% feilfrekvens. Du kan også ha forskjellige terskler for forskjellige feil, for eksempel en terskel på 10 for tidsavbrudd, men 3 for tilkoblingsfeil.

eksemplet jeg har vist er en bryter for synkrone samtaler, men effektbrytere er også nyttige for asynkron kommunikasjon. En vanlig teknikk her er å sette alle forespørsler på en kø, som leverandøren bruker med sin hastighet – en nyttig teknikk for å unngå overbelastning av servere. I dette tilfellet bryter kretsen når køen fylles opp.

på egen hånd bidrar effektbrytere til å redusere ressurser bundet opp i operasjoner som sannsynligvis vil mislykkes. Du unngår å vente på timeouts for klienten, og en ødelagt krets unngår å sette last på en sliter server. Jeg snakker her om eksterne samtaler, som er et vanlig tilfelle for effektbrytere, men de kan brukes i alle situasjoner der du vil beskytte deler av et system mot feil i andre deler.

Effektbrytere er et verdifullt sted for overvåking. Enhver endring i breaker tilstand bør logges og breakers bør avsløre detaljer om deres tilstand for dypere overvåking. Breaker atferd er ofte en god kilde til advarsler om dypere problemer i miljøet. Driftspersonalet skal kunne reise eller tilbakestille brytere.

Brytere på egen hånd er verdifulle, men klienter som bruker dem, må reagere på bryterfeil. Som med enhver ekstern påkalling må du vurdere hva du skal gjøre i tilfelle feil. Mislykkes det operasjonen du utfører, eller er det løsninger du kan gjøre? Et kredittkort autorisasjon kan bli satt på en kø for å håndtere senere, unnlatelse av å få noen data kan reduseres ved å vise noen foreldede data som er god nok til å vise.

Videre Lesing

netflix tech blog inneholder mye nyttig informasjon om å forbedre påliteligheten til systemer med mange tjenester. Deres Dependency Command snakker om bruk av effektbrytere og en trådbassenggrense.

Netflix har Åpen Kilde Hystrix, et sofistikert verktøy for å håndtere ventetid og feiltoleranse for distribuerte systemer. Det inkluderer en implementering av kretsbrytermønsteret med trådbassenggrensen

Det finnes andre åpen kildekode-implementeringer av kretsbrytermønsteret I Ruby, Java, Grails Plugin, C#, AspectJ og Scala

Takk

Pavel Shpak oppdaget og rapporterte en feil i eksempelkoden

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.