het is gebruikelijk dat softwaresystemen op afstand aanroepen naar software die in verschillende processen draait, waarschijnlijk op verschillende machines in een netwerk. Een van de grote verschillen tussen in-memory calls en remote calls is dat remote calls kunnen mislukken, of hangen zonder een reactie tot een bepaalde time-out limiet is bereikt. Wat is erger als je veel bellers op een niet-reagerende leverancier, dan kunt u opraken van kritische middelen leidt tot trapsgewijze storingen in meerdere systemen. In zijn uitstekende boek Release It populariseerde Michael Nygard het patroon van de stroomonderbrekers om dit soort catastrofale cascade te voorkomen.
het basisidee achter de stroomonderbreker is zeer eenvoudig. U wikkelt een beveiligde functie oproep in een stroomonderbreker object, dat controleert op storingen. Zodra de storingen bereiken een bepaalde drempel, de stroomonderbreker struikelt, en alle verdere oproepen naar de stroomonderbreker terug met een fout, zonder dat de beveiligde oproep wordt gemaakt op alle. Meestal wilt u ook een soort monitor alert als de stroomonderbreker struikelt.
hier is een eenvoudig voorbeeld van dit gedrag in Ruby, bescherming tegen time-outs.
ik stel de breker in met een blok (Lambda) dat de beveiligde aanroep is.
cb = CircuitBreaker.new {|arg| @supplier.func arg}
de breker slaat het blok op, initialiseert verschillende parameters (voor drempels, time-outs en monitoring) en stelt de breker in zijn gesloten toestand terug.
Klasse CircuitBreaker…
attr_accessor :invocation_timeout, :failure_threshold, :monitor def initialize &block @circuit = block @invocation_timeout = 0.01 @failure_threshold = 5 @monitor = acquire_monitor reset end
het aanroepen van de stroomonderbreker zal het onderliggende blok aanroepen als het circuit gesloten is, maar geeft een fout terug als het open is
# client code aCircuitBreaker.call(5)
Klasse CircuitBreaker…
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
als we een time-out krijgen, verhogen we de foutteller, succesvolle oproepen resetten het terug naar nul.
Klasse CircuitBreaker…
def record_failure @failure_count += 1 @monitor.alert(:open_circuit) if :open == state end def reset @failure_count = 0 @monitor.alert :reset_circuit end
I bepaal de toestand van de breker door het aantal storingen te vergelijken met de drempelwaarde
Klasse CircuitBreaker…
def state (@failure_count >= @failure_threshold) ? :open : :closed end
deze eenvoudige stroomonderbreker vermijdt het maken van de beveiligde oproep wanneer het circuit open is, maar zou een externe interventie nodig hebben om het te resetten wanneer het weer goed gaat. Dit is een redelijke aanpak met elektrische stroomonderbrekers in gebouwen, maar voor software stroomonderbrekers kunnen we de stroomonderbreker zelf laten detecteren als de onderliggende gesprekken weer werken. We kunnen dit zelf-resetten gedrag implementeren door de beveiligde oproep opnieuw te proberen na een geschikte interval, en de breaker te resetten als het lukt.
het maken van dit soort breaker betekent het toevoegen van een drempel voor het proberen van de reset en het opzetten van een variabele om de tijd van de laatste fout vast te houden.
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
er is nu een derde staat aanwezig-half open-wat betekent dat het circuit klaar is om een echte call als proef te maken om te zien of het probleem is opgelost.
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
gevraagd om te bellen in de half-open toestand resulteert in een proefoproep, die ofwel de breaker reset als het succesvol is of de time-out herstart als dit niet het geval is.
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
dit voorbeeld is een eenvoudige verklarende, in de praktijk stroomonderbrekers bieden een goed beetje meer functies en parametrering. Vaak beschermen ze tegen een reeks fouten die beveiligde gesprekken kunnen oproepen, zoals netwerkverbindingsfouten. Niet alle fouten zouden het circuit moeten uitschakelen, sommige zouden normale storingen moeten weerspiegelen en behandeld moeten worden als onderdeel van de reguliere logica.
met veel verkeer kunt u problemen hebben met veel oproepen die wachten op de initiële time-out. Omdat gesprekken op afstand vaak traag zijn, is het vaak een goed idee om elke oproep op een andere thread te zetten met behulp van een toekomst of belofte om de resultaten af te handelen wanneer ze terugkomen. Door deze draden uit een draadpool te trekken, kunt u ervoor zorgen dat het circuit breekt wanneer de draadpool is uitgeput.
het voorbeeld toont een eenvoudige manier om de breaker te trippen — een telling die wordt gereset bij een succesvolle aanroep. Een meer geavanceerde aanpak zou kunnen kijken naar de frequentie van fouten, struikelen zodra je, laten we zeggen, een 50% faalpercentage. U kunt ook verschillende drempels voor verschillende fouten, zoals een drempel van 10 voor time-outs, maar 3 voor verbindingsfouten.
het voorbeeld dat ik heb getoond is een stroomonderbreker voor synchrone oproepen, maar stroomonderbrekers zijn ook nuttig voor asynchrone communicatie. Een veelgebruikte techniek hier is om alle verzoeken in een wachtrij te plaatsen, die de leverancier met zijn snelheid verbruikt – een handige techniek om overbelasting van servers te voorkomen. In dit geval breekt het circuit wanneer de wachtrij vol is.
stroomonderbrekers dragen op zichzelf bij tot het verminderen van de middelen die nodig zijn voor operaties die waarschijnlijk uitvallen. Je vermijdt het wachten op timeouts voor de client, en een gebroken circuit vermijdt het plaatsen van belasting op een worstelende server. Ik heb het hier over gesprekken op afstand, die een veel voorkomend geval zijn voor stroomonderbrekers, maar ze kunnen worden gebruikt in elke situatie waar je delen van een systeem wilt beschermen tegen storingen in andere delen.
stroomonderbrekers zijn een waardevolle plaats voor monitoring. Elke verandering in breaker status moet worden geregistreerd en breakers moeten onthullen details van hun toestand voor diepere monitoring. Breker gedrag is vaak een goede bron van waarschuwingen over diepere problemen in de omgeving. Operations personeel moet in staat zijn om te schakelen of te resetten breakers.
stroomonderbrekers op zichzelf zijn waardevol, maar clients die ze gebruiken, moeten reageren op storingen in stroomonderbrekers. Zoals bij elke aanroep op afstand moet u overwegen wat te doen in geval van een storing. Mislukt het de operatie die je uitvoert, of zijn er oplossingen die je kunt doen? Een creditcard autorisatie kan in een wachtrij worden gezet om later te behandelen, het niet krijgen van sommige gegevens kan worden beperkt door het tonen van een aantal verouderde gegevens die goed genoeg is om weer te geven.
verder lezen
het Netflix tech blog bevat veel nuttige informatie over het verbeteren van de betrouwbaarheid van systemen met veel diensten. Hun afhankelijkheid Commando praat over het gebruik van stroomonderbrekers en een thread pool limiet.
Netflix heeft open-source Hystrix, een geavanceerde tool voor het omgaan met latency en fouttolerantie voor gedistribueerde systemen. Het bevat een implementatie van het stroomonderbrekerpatroon met de thread pool limiet
er zijn andere open-source implementaties van het stroomonderbrekerpatroon in Ruby, Java, Grails Plugin, C#, AspectJ en Scala
Dankbetuigingen
Pavel Shpak zag en meldde een bug in de voorbeeldcode