Circuit breaker (шаблон проєктування) — Вікіпедія

Circuit breaker — патерн проєктування, який використовується для виявлення відмов та надає логіку запобіганню постійних повторів.

Проблема[ред. | ред. код]

Нехай, дано два сервіси, які взаємодіють між собою. Результат роботи одного сервісу напряму залежить від іншого.

Основний сервіс дізнається про несправність допоміжного із певною затримкою. Однак на момент очікування, основний сервіс витрачає свої ресурси, такі як пам'ять, процесорний час, кількість доступних потоків виконання, тощо. Якщо кількість запитів на основний сервіс перевищує затримку очікування відповіді про несправність допоміжного сервісу, із часом основний сервіс вичерпає свої ресурси й також вийде із ладу.

Вирішення[ред. | ред. код]

Необхідно додати проміжний сервіс, який заздалегідь перериватиме невдалі запити, а також слідкуватиме за відновленням роботи допоміжного сервісу.

Алгоритм[ред. | ред. код]

Приклад станів у Circuit breaker шаблоні

Closed (з'єднаний зв'язок)

Система перебуває у з'єднаному стані тоді коли допоміжний сервіс успішно відповідає на запити основного сервісу.

  1. Проміжний сервіс пересилає запити від основного до допоміжного сервісу.
  2. Проміжний сервіс веде кількість невдалих запитів. Якщо допоміжний сервіс відповів із помилкою, то проміжний сервіс збільшує значення в лічильнику.
  3. Якщо кількість невдалих спроб перевищує максимальну протягом певного періоду часу, то проміжний сервіс переходить в стан Open.

Open (зв'язок із розривами)

Система перебуває у стані розриву. Задача цього стану не навантажувати допоміжний сервіс запитами та дати йому час на відновлення. При цьому основний сервіс отримує відповідь про несправність без жодних затримок.

  1. Проміжний сервіс отримує запити від основного сервісу та миттєво завершує їх із помилкою.
  2. Проміжний сервіс запускає таймер (час необхідний допоміжному сервісу для відновлення), і по завершені цього часу переходить в стан Half Open. Перехід в цей стан можна реалізувати також іншим чином, наприклад, від кількості запитів, тощо.

Half Open (відновлення зв'язку)

На цьому етапі ми не знаємо напевно чи допоміжний сервіс відновився, чи досі перебуває в аварійному стані.

  1. Дозволяємо запит від основного сервісу до допоміжного.
    1. Якщо запит успішний, то переходимо в стан Close.
    2. Якщо запит завершився помилкою, то переходимо в стан 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#[ред. | ред. код]

Див. також[ред. | ред. код]

Джерела[ред. | ред. код]