CircuitBreaker

często systemy programowe wykonują zdalne połączenia z oprogramowaniem działającym w różnych procesach, prawdopodobnie na różnych maszynach w sieci. Jedną z dużych różnic między połączeniami w pamięci i połączeniami zdalnymi jest to, że połączenia zdalne mogą się nie powieść lub zawiesić się bez odpowiedzi, dopóki nie zostanie osiągnięty limit czasu. Co gorsza, jeśli masz wielu rozmówców u dostawcy, który nie odpowiada, możesz zabraknąć krytycznych zasobów, co prowadzi do kaskadowych awarii w wielu systemach. W swojej znakomitej książce Release it, Michael Nygard spopularyzował wzór wyłącznika, aby zapobiec tego rodzaju katastrofalnej kaskadzie.

podstawowa idea wyłącznika jest bardzo prosta. Zawijasz chronione wywołanie funkcji w obiekcie wyłącznika, który monitoruje awarie. Gdy awarie osiągną określony próg, wyłącznik wyłącza się, a wszystkie dalsze połączenia do wyłącznika powracają z błędem, bez wykonywania chronionego połączenia. Zazwyczaj będziesz również chciał jakiegoś monitora, jeśli wyłącznik się wyłączy.

Oto prosty przykład tego zachowania w Rubim, chroniącego przed przekroczeniem czasu.

ustawiłem wyłącznik z blokiem (Lambda), który jest wywołaniem chronionym.

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

wyłącznik przechowuje blok, inicjuje różne parametry (progi, limity czasowe i monitorowanie) i resetuje wyłącznik do stanu zamkniętego.

class 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

wywołanie wyłącznika wywoła podstawowy blok, jeśli obwód jest zamknięty, ale zwróci błąd, jeśli jest otwarty

# client code aCircuitBreaker.call(5)

CircuitBreaker klasy…

 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

jeśli uzyskamy limit czasu, zwiększamy licznik awarii, pomyślne połączenia resetują go z powrotem do zera.

class 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

określam stan wyłącznika porównując liczbę awarii do progu

CircuitBreaker klasy…

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

ten prosty wyłącznik unika wykonywania chronionego połączenia, gdy obwód jest otwarty, ale potrzebuje zewnętrznej interwencji, aby go zresetować, gdy wszystko jest dobrze. Jest to rozsądne podejście z wyłącznikami elektrycznymi w budynkach, ale w przypadku wyłączników programowych możemy mieć sam wyłącznik wykryć, czy podstawowe połączenia działają ponownie. Możemy zaimplementować to samo-Resetowanie przez ponowne spróbowanie wywołania protected po odpowiednim interwale i zresetowanie wyłącznika, jeśli się powiedzie.

utworzenie tego rodzaju wyłącznika oznacza dodanie progu próby resetu i ustawienie zmiennej, która zatrzyma czas ostatniego błędu.

Class 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

jest teraz obecny trzeci stan – pół otwarty – co oznacza, że obwód jest gotowy do wykonania prawdziwego połączenia jako próba, aby sprawdzić, czy problem został rozwiązany.

Class 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

poproszony o wywołanie w stanie półotwartym powoduje wywołanie próbne, które albo zresetuje wyłącznik, jeśli się powiedzie, albo ponownie uruchomi limit czasu, jeśli nie.

Class 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

ten przykład jest prostym wyjaśnieniem, w praktyce wyłączniki zapewniają nieco więcej funkcji i parametryzacji. Często będą one chronić przed szeregiem błędów, które chronione wywołanie może wywołać, takich jak awarie połączenia sieciowego. Nie wszystkie błędy powinny potknąć Obwód, niektóre powinny odzwierciedlać normalne awarie i być rozpatrywane jako część regularnej logiki.

przy dużym natężeniu ruchu możesz mieć problemy z wieloma połączeniami tylko czekającymi na początkowy limit czasu. Ponieważ połączenia zdalne są często powolne, często dobrym pomysłem jest umieszczenie każdego połączenia w innym wątku, używając przyszłości lub obietnicy, aby obsłużyć wyniki, gdy wrócą. Rysując te wątki z puli wątków, możesz zorganizować przerwanie obwodu po wyczerpaniu puli wątków.

przykład pokazuje prosty sposób na wyłączenie wyłącznika-licznik, który resetuje się po pomyślnym wywołaniu. Bardziej wyrafinowane podejście może spojrzeć na częstotliwość błędów, potknięcia, gdy pojawi się, powiedzmy, 50% wskaźnik awaryjności. Możesz również mieć różne progi dla różnych błędów, na przykład próg 10 dla przekroczenia czasu, ale 3 dla awarii połączenia.

przykład, który pokazałem, to wyłącznik dla połączeń synchronicznych, ale wyłączniki są również przydatne do komunikacji asynchronicznej. Powszechną techniką jest umieszczanie wszystkich żądań w kolejce, którą dostawca zużywa z szybkością – przydatna technika, aby uniknąć przeciążenia serwerów. W tym przypadku obwód przerywa się, gdy kolejka się zapełni.

same wyłączniki pomagają zmniejszyć zasoby związane z operacjami, które mogą się nie powieść. Unikasz czekania na timeouts dla klienta, a uszkodzony Obwód unika obciążenia na trudnym serwerze. Mówię tutaj o połączeniach zdalnych, które są częstym przypadkiem wyłączników, ale mogą być używane w każdej sytuacji, w której chcesz chronić części systemu przed awariami w innych częściach.

wyłączniki są cennym miejscem do monitorowania. Każda zmiana stanu wyłącznika powinna być rejestrowana, a wyłączniki powinny ujawniać szczegóły swojego stanu w celu głębszego monitorowania. Zachowanie wyłącznika jest często dobrym źródłem ostrzeżeń o głębszych problemach w środowisku. Personel operacyjny powinien mieć możliwość wyłączenia lub zresetowania wyłączników.

Wyłączniki same w sobie są cenne, ale klienci z nich korzystający muszą reagować na awarie wyłączników. Podobnie jak w przypadku każdego zdalnego wywołania, musisz rozważyć, co zrobić w przypadku awarii. Czy to nie powiedzie się operacji, którą wykonujesz, czy są obejścia, które możesz zrobić? Autoryzacja karty kredytowej może zostać umieszczona w kolejce, aby później poradzić sobie z tym problemem, brak dostępu do niektórych danych może zostać złagodzony poprzez pokazanie starych danych, które są wystarczająco dobre do wyświetlenia.

Czytaj dalej

blog technologiczny netflix zawiera wiele przydatnych informacji na temat poprawy niezawodności systemów z dużą ilością usług. Ich polecenie zależności mówi o użyciu wyłączników i limitu puli wątków.

Netflix ma open-source Hystrix, zaawansowane narzędzie do radzenia sobie z opóźnieniami i odpornością na awarie systemów rozproszonych. Zawiera implementację wzorca wyłącznika z limitem puli wątków

istnieją inne implementacje open-source wzorca wyłącznika w Ruby, Java, Grails Plugin, C#, AspectJ i Scala

podziękowania

Paweł Szpak zauważył i zgłosił błąd w przykładzie kodu

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.