Circuit breaker — патерн проєктування, який використовується для виявлення відмов та надає логіку запобіганню постійних повторів.
Проблема
Нехай, дано два сервіси, які взаємодіють між собою. Результат роботи одного сервісу напряму залежить від іншого.
Основний сервіс дізнається про несправність допоміжного із певною затримкою. Однак на момент очікування, основний сервіс витрачає свої ресурси, такі як пам'ять, процесорний час, кількість доступних потоків виконання, тощо. Якщо кількість запитів на основний сервіс перевищує затримку очікування відповіді про несправність допоміжного сервісу, із часом основний сервіс вичерпає свої ресурси й також вийде із ладу.
Вирішення
Необхідно додати проміжний сервіс, який заздалегідь перериватиме невдалі запити, а також слідкуватиме за відновленням роботи допоміжного сервісу.
Алгоритм
Closed (з'єднаний зв'язок)
Система перебуває у з'єднаному стані тоді коли допоміжний сервіс успішно відповідає на запити основного сервісу.
- Проміжний сервіс пересилає запити від основного до допоміжного сервісу.
- Проміжний сервіс веде кількість невдалих запитів. Якщо допоміжний сервіс відповів із помилкою, то проміжний сервіс збільшує значення в лічильнику.
- Якщо кількість невдалих спроб перевищує максимальну протягом певного періоду часу, то проміжний сервіс переходить в стан Open.
Open (зв'язок із розривами)
Система перебуває у стані розриву. Задача цього стану не навантажувати допоміжний сервіс запитами та дати йому час на відновлення. При цьому основний сервіс отримує відповідь про несправність без жодних затримок.
- Проміжний сервіс отримує запити від основного сервісу та миттєво завершує їх із помилкою.
- Проміжний сервіс запускає таймер (час необхідний допоміжному сервісу для відновлення), і по завершені цього часу переходить в стан Half Open. Перехід в цей стан можна реалізувати також іншим чином, наприклад, від кількості запитів, тощо.
Half Open (відновлення зв'язку)
На цьому етапі ми не знаємо напевно чи допоміжний сервіс відновився, чи досі перебуває в аварійному стані.
- Дозволяємо запит від основного сервісу до допоміжного.
- Якщо запит успішний, то переходимо в стан Close.
- Якщо запит завершився помилкою, то переходимо в стан Open.
Опис мовою C#
Нехай, дано сервіси які взаємодіють між собою.
public interface IService { // виконання операції string GetValue(); // виконання операції базуючись на результаті від іншого сервісу string GetModifiedValue(IService service) { try { return $"From another service: {service.GetValue()}"; } catch { return "Call to another service failed"; } } }
У той час, як один із сервісів успішно виконує свою операцію. Інший має шанс аварійного завершення. При цьому присутня затримка, що впливає на роботу основного сервісу.
public class ServiceA : IService { public string GetValue() { return "Service A"; } } public class ServiceB : IService { public string GetValue() { // 50% шанс аварійного завершення if (new Random().NextDouble() > 0.5) { // імітуємо довготривалу роботу Thread.Sleep(5000); throw new InvalidOperationException(); } return "Service B"; } }
Таким чином несправності в допоміжному сервісі блокують основний.
static void Main(string[] args) { IService serviceA = new ServiceA(); IService serviceB = new ServiceB(); // несправності в допоміжному сервісі блокують основний for (int i = 0; i < 20; ++i) { Console.WriteLine($"Request {i}"); Console.WriteLine(serviceA.GetValue()); Console.WriteLine(serviceA.GetModifiedValue(serviceB)); Console.WriteLine(); } }
Додамо сервіс, який корегуватиме стан систему.
// опишемо стани системи public enum CircuitBreakerStateEnum { Closed = 0, Open = 1, HalfOpen = 2, } interface ICircuitBreaker { CircuitBreakerStateEnum State { get; } int FailThreshold { get; set; } // максимальна кількість невдалих запитів потрібних для переходу в Open стан int RetriesThreshold { get; set; } // максимальна кількість запитів потрібних для переходу в HalfOpen стан }
Проміжний сервіс матиме наступний вигляд:
public class ServiceBProxy : IService, ICircuitBreaker { private readonly IService _service; public ServiceBProxy(IService service) { _service = service; } public string GetValue() { if (State == CircuitBreakerStateEnum.Closed) { try { // делегуємо запит допоміжному сервісу return _service.GetValue(); } catch (Exception) { // підраховуємо невдалі запити та при потребі переходимо в Open стан ++_currentFailAttempt; if (_currentFailAttempt >= FailThreshold) { _currentFailAttempt = 0; State = CircuitBreakerStateEnum.Open; } throw; } } else if (State == CircuitBreakerStateEnum.Open) { // рахуємо запити і при потребі переходимо в HalfOpen стан ++_retriesAttempt; if (_retriesAttempt > RetriesThreshold) { _retriesAttempt = 0; State = CircuitBreakerStateEnum.HalfOpen; } // без затримки повідомляємо про помилку в системі throw new InvalidOperationException(); } else // if (State == CircuitBreakerStateEnum.HalfOpen) { try { // робимо запит // у разі успішного виконання переходимо в стан Closed var value = _service.GetValue(); State = CircuitBreakerStateEnum.Closed; return value; } catch (Exception) { // при помилці переходимо в стан Open State = CircuitBreakerStateEnum.Open; throw; } } } // поля необхідні для роботи CircuitBreaker private int _currentFailAttempt; private int _retriesAttempt; // можливо таймер public CircuitBreakerStateEnum State { get; private set; } = CircuitBreakerStateEnum.Closed; public int FailThreshold { get; set; } = 2; public int RetriesThreshold { get; set; } = 3; }
Реалізація
C#
using System; using System.Threading; namespace CircuitBreakerPattern { public interface IService { // виконання операції string GetValue(); // виконання операції базуючись на результаті від іншого сервісу string GetModifiedValue(IService service) { try { return $"From another service: {service.GetValue()}"; } catch { return "Call another service failed"; } } } public class ServiceA : IService { public string GetValue() { return "Service A"; } } public class ServiceB : IService { public string GetValue() { // 50% шанс аварійного завершення if (new Random().NextDouble() > 0.5) { // імітуємо довготривалу роботу Thread.Sleep(5000); throw new InvalidOperationException(); } return "Service B"; } } public enum CircuitBreakerStateEnum { Closed = 0, Open = 1, HalfOpen = 2, } interface ICircuitBreaker { CircuitBreakerStateEnum State { get; } int FailThreshold { get; set; } // максимальна кількість невдалих запитів потрібних для переходу в Open стан // оригінальний алгоритм передбачає використання таймеру int RetriesThreshold { get; set; } // максимальна кількість запитів потрібних для переходу в HalfOpen стан } public class ServiceBProxy : IService, ICircuitBreaker { private readonly IService _service; public ServiceBProxy(IService service) { _service = service; } private int _currentFailAttempt; private int _retriesAttempt; public CircuitBreakerStateEnum State { get; private set; } = CircuitBreakerStateEnum.Closed; public int FailThreshold { get; set; } = 2; public int RetriesThreshold { get; set; } = 3; public string GetValue() { if (State == CircuitBreakerStateEnum.Closed) { return GetValueInClosedState(); } else if (State == CircuitBreakerStateEnum.Open) { return GetValueInOpenState(); } else // if (State == CircuitBreakerStateEnum.HalfOpen) { return GetValueInHalfOpenState(); } } private string GetValueInClosedState() { try { // делегуємо запит допоміжному сервісу return _service.GetValue(); } catch { // підраховуємо невдалі запити ++_currentFailAttempt; // при перевищені ліміту переходимо в Open стан if (_currentFailAttempt >= FailThreshold) Open(); throw; } } private string GetValueInOpenState() { // рахуємо запити ++_retriesAttempt; // при перевищені ліміту переходимо в HalfOpen стан if (_retriesAttempt > RetriesThreshold) HalfOpen(); // без затримки повідомляємо про помилку в системі throw new InvalidOperationException(); } private string GetValueInHalfOpenState() { try { // робимо запит var value = _service.GetValue(); // у разі успішного виконання переходимо в стан Closed Close(); return value; } catch { // при помилці переходимо в стан Open Open(); throw; } } private void Open() { ResetCounter(); State = CircuitBreakerStateEnum.Open; } private void HalfOpen() { ResetCounter(); State = CircuitBreakerStateEnum.HalfOpen; } private void Close() { ResetCounter(); State = CircuitBreakerStateEnum.Closed; } private void ResetCounter() { _currentFailAttempt = 0; _retriesAttempt = 0; } } class Program { static void Main(string[] args) { IService serviceA = new ServiceA(); IService serviceB = new ServiceB(); // обгортаємо допоміжний сервіс, proxy-сервісом, який реалізовує логіку Circuit Breaker serviceB = new ServiceBProxy(serviceB); // несправності в допоміжному сервісі блокують основний for (int i = 0; i < 20; ++i) { Console.WriteLine($"Request {i}"); Console.WriteLine(serviceA.GetValue()); Console.WriteLine(serviceA.GetModifiedValue(serviceB)); Console.WriteLine(); } } } }
Див. також
Джерела
- Circuit Breaker [ 27 червня 2020 у Wayback Machine.]
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Circuit breaker patern proyektuvannya yakij vikoristovuyetsya dlya viyavlennya vidmov ta nadaye logiku zapobigannyu postijnih povtoriv ProblemaNehaj dano dva servisi yaki vzayemodiyut mizh soboyu Rezultat roboti odnogo servisu napryamu zalezhit vid inshogo Osnovnij servis diznayetsya pro nespravnist dopomizhnogo iz pevnoyu zatrimkoyu Odnak na moment ochikuvannya osnovnij servis vitrachaye svoyi resursi taki yak pam yat procesornij chas kilkist dostupnih potokiv vikonannya tosho Yaksho kilkist zapitiv na osnovnij servis perevishuye zatrimku ochikuvannya vidpovidi pro nespravnist dopomizhnogo servisu iz chasom osnovnij servis vicherpaye svoyi resursi j takozh vijde iz ladu VirishennyaNeobhidno dodati promizhnij servis yakij zazdalegid pererivatime nevdali zapiti a takozh slidkuvatime za vidnovlennyam roboti dopomizhnogo servisu AlgoritmPriklad staniv u Circuit breaker shabloni Closed z yednanij zv yazok Sistema perebuvaye u z yednanomu stani todi koli dopomizhnij servis uspishno vidpovidaye na zapiti osnovnogo servisu Promizhnij servis peresilaye zapiti vid osnovnogo do dopomizhnogo servisu Promizhnij servis vede kilkist nevdalih zapitiv Yaksho dopomizhnij servis vidpoviv iz pomilkoyu to promizhnij servis zbilshuye znachennya v lichilniku Yaksho kilkist nevdalih sprob perevishuye maksimalnu protyagom pevnogo periodu chasu to promizhnij servis perehodit v stan Open Open zv yazok iz rozrivami Sistema perebuvaye u stani rozrivu Zadacha cogo stanu ne navantazhuvati dopomizhnij servis zapitami ta dati jomu chas na vidnovlennya Pri comu osnovnij servis otrimuye vidpovid pro nespravnist bez zhodnih zatrimok Promizhnij servis otrimuye zapiti vid osnovnogo servisu ta mittyevo zavershuye yih iz pomilkoyu Promizhnij servis zapuskaye tajmer chas neobhidnij dopomizhnomu servisu dlya vidnovlennya i po zaversheni cogo chasu perehodit v stan Half Open Perehid v cej stan mozhna realizuvati takozh inshim chinom napriklad vid kilkosti zapitiv tosho Half Open vidnovlennya zv yazku Na comu etapi mi ne znayemo napevno chi dopomizhnij servis vidnovivsya chi dosi perebuvaye v avarijnomu stani Dozvolyayemo zapit vid osnovnogo servisu do dopomizhnogo Yaksho zapit uspishnij to perehodimo v stan Close Yaksho zapit zavershivsya pomilkoyu to perehodimo v stan Open Opis movoyu C Nehaj dano servisi yaki vzayemodiyut mizh soboyu public interface IService vikonannya operaciyi string GetValue vikonannya operaciyi bazuyuchis na rezultati vid inshogo servisu string GetModifiedValue IService service try return From another service service GetValue catch return Call to another service failed U toj chas yak odin iz servisiv uspishno vikonuye svoyu operaciyu Inshij maye shans avarijnogo zavershennya Pri comu prisutnya zatrimka sho vplivaye na robotu osnovnogo servisu public class ServiceA IService public string GetValue return Service A public class ServiceB IService public string GetValue 50 shans avarijnogo zavershennya if new Random NextDouble gt 0 5 imituyemo dovgotrivalu robotu Thread Sleep 5000 throw new InvalidOperationException return Service B Takim chinom nespravnosti v dopomizhnomu servisi blokuyut osnovnij static void Main string args IService serviceA new ServiceA IService serviceB new ServiceB nespravnosti v dopomizhnomu servisi blokuyut osnovnij for int i 0 i lt 20 i Console WriteLine Request i Console WriteLine serviceA GetValue Console WriteLine serviceA GetModifiedValue serviceB Console WriteLine Dodamo servis yakij koreguvatime stan sistemu opishemo stani sistemi public enum CircuitBreakerStateEnum Closed 0 Open 1 HalfOpen 2 interface ICircuitBreaker CircuitBreakerStateEnum State get int FailThreshold get set maksimalna kilkist nevdalih zapitiv potribnih dlya perehodu v Open stan int RetriesThreshold get set maksimalna kilkist zapitiv potribnih dlya perehodu v HalfOpen stan Promizhnij servis matime nastupnij viglyad public class ServiceBProxy IService ICircuitBreaker private readonly IService service public ServiceBProxy IService service service service public string GetValue if State CircuitBreakerStateEnum Closed try deleguyemo zapit dopomizhnomu servisu return service GetValue catch Exception pidrahovuyemo nevdali zapiti ta pri potrebi perehodimo v Open stan currentFailAttempt if currentFailAttempt gt FailThreshold currentFailAttempt 0 State CircuitBreakerStateEnum Open throw else if State CircuitBreakerStateEnum Open rahuyemo zapiti i pri potrebi perehodimo v HalfOpen stan retriesAttempt if retriesAttempt gt RetriesThreshold retriesAttempt 0 State CircuitBreakerStateEnum HalfOpen bez zatrimki povidomlyayemo pro pomilku v sistemi throw new InvalidOperationException else if State CircuitBreakerStateEnum HalfOpen try robimo zapit u razi uspishnogo vikonannya perehodimo v stan Closed var value service GetValue State CircuitBreakerStateEnum Closed return value catch Exception pri pomilci perehodimo v stan Open State CircuitBreakerStateEnum Open throw polya neobhidni dlya roboti CircuitBreaker private int currentFailAttempt private int retriesAttempt mozhlivo tajmer public CircuitBreakerStateEnum State get private set CircuitBreakerStateEnum Closed public int FailThreshold get set 2 public int RetriesThreshold get set 3 RealizaciyaC Priklad realizaciyi na movi S using System using System Threading namespace CircuitBreakerPattern public interface IService vikonannya operaciyi string GetValue vikonannya operaciyi bazuyuchis na rezultati vid inshogo servisu string GetModifiedValue IService service try return From another service service GetValue catch return Call another service failed public class ServiceA IService public string GetValue return Service A public class ServiceB IService public string GetValue 50 shans avarijnogo zavershennya if new Random NextDouble gt 0 5 imituyemo dovgotrivalu robotu Thread Sleep 5000 throw new InvalidOperationException return Service B public enum CircuitBreakerStateEnum Closed 0 Open 1 HalfOpen 2 interface ICircuitBreaker CircuitBreakerStateEnum State get int FailThreshold get set maksimalna kilkist nevdalih zapitiv potribnih dlya perehodu v Open stan originalnij algoritm peredbachaye vikoristannya tajmeru int RetriesThreshold get set maksimalna kilkist zapitiv potribnih dlya perehodu v HalfOpen stan public class ServiceBProxy IService ICircuitBreaker private readonly IService service public ServiceBProxy IService service service service private int currentFailAttempt private int retriesAttempt public CircuitBreakerStateEnum State get private set CircuitBreakerStateEnum Closed public int FailThreshold get set 2 public int RetriesThreshold get set 3 public string GetValue if State CircuitBreakerStateEnum Closed return GetValueInClosedState else if State CircuitBreakerStateEnum Open return GetValueInOpenState else if State CircuitBreakerStateEnum HalfOpen return GetValueInHalfOpenState private string GetValueInClosedState try deleguyemo zapit dopomizhnomu servisu return service GetValue catch pidrahovuyemo nevdali zapiti currentFailAttempt pri perevisheni limitu perehodimo v Open stan if currentFailAttempt gt FailThreshold Open throw private string GetValueInOpenState rahuyemo zapiti retriesAttempt pri perevisheni limitu perehodimo v HalfOpen stan if retriesAttempt gt RetriesThreshold HalfOpen bez zatrimki povidomlyayemo pro pomilku v sistemi throw new InvalidOperationException private string GetValueInHalfOpenState try robimo zapit var value service GetValue u razi uspishnogo vikonannya perehodimo v stan Closed Close return value catch pri pomilci perehodimo v stan Open Open throw private void Open ResetCounter State CircuitBreakerStateEnum Open private void HalfOpen ResetCounter State CircuitBreakerStateEnum HalfOpen private void Close ResetCounter State CircuitBreakerStateEnum Closed private void ResetCounter currentFailAttempt 0 retriesAttempt 0 class Program static void Main string args IService serviceA new ServiceA IService serviceB new ServiceB obgortayemo dopomizhnij servis proxy servisom yakij realizovuye logiku Circuit Breaker serviceB new ServiceBProxy serviceB nespravnosti v dopomizhnomu servisi blokuyut osnovnij for int i 0 i lt 20 i Console WriteLine Request i Console WriteLine serviceA GetValue Console WriteLine serviceA GetModifiedValue serviceB Console WriteLine Div takozhShabloni proyektuvannya programnogo zabezpechennyaDzherelaCircuit Breaker 27 chervnya 2020 u Wayback Machine