💬 Что из себя представляет паттерн Circuit Breaker, применяемый в облачной разработке?



Circuit Breaker автоматически отключает сервисные функции в ответ на вероятную неисправность, чтобы предотвратить более крупные или каскадные отказы, устранить повторяющиеся ошибки и обеспечить разумную реакцию на ошибки.



📌 Компоненты



Circuit — функция, взаимодействующая с сервисом.

Breaker — функция-замыкание с той же сигнатурой, что и Circuit.



По сути, Circuit Breaker — это просто специализированный паттерн Adapter, в котором функция Breaker обертывает Circuit и добавляет дополнительную логику обработки ошибок.



Подобно электрическому выключателю, от которого этот паттерн получил свое название, Breaker имеет два возможных состояния: замкнуто и разомкнуто. В замкнутом состоянии все работает как обычно. Все запросы, полученные от клиента с помощью Breaker, передаются в Circuit без изменений, а все ответы от Circuit возвращаются обратно клиенту. В разомкнутом состоянии Breaker не передает запросы в Circuit, а просто «быстро терпит неудачу», возвращая информативное сообщение об ошибке.



📌 Пример кода



Начнем с определения типа Circuit, определяющего сигнатуру функции, которая взаимодействует с БД или другой вышестоящей службой. На практике эта сигнатура может быть любой, соответствующей нашим требованиям.



Однако она должна включать ошибку в список возвращаемых значений:



type Circuit func(context.Context) (string, error)





В примере функция Circuit принимает экземпляр Context. Функция Breaker принимает любую функцию, соответствующую определению типа Circuit, и целое число без знака, представляющее количество отказов, следующих друг за другом, после которых должно произойти автоматическое размыкание цепи. В ответ она возвращает другую функцию, которая также соответствует определению типа Circuit:



func Breaker(circuit Circuit, failureThreshold uint) Circuit { 

var consecutiveFailures int = 0

var lastAttempt = time.Now()

var m sync.RWMutex



return func(ctx context.Context) (string, error) {

m.RLock() // Установить "блокировку чтения"

d := consecutiveFailures - int(failureThreshold)



if d >= 0 {

shouldRetryAt := lastAttempt.Add(time.Second * 2 << d)

if !time.Now().After(shouldRetryAt) {

m.RUnlock()

return "", errors.New("service unreachable")

}

}



m.RUnlock() // Освободить блокировку чтения



response, err := circuit(ctx) // Послать запрос, как обычно



m.Lock() // Заблокировать общие ресурсы

defer m.Unlock()



lastAttempt = time.Now() // Зафиксировать время попытки

if err != nil { // Если Circuit вернула ошибку,

consecutiveFailures++ // увеличить счетчик ошибок

return response, err // и вернуть ошибку

}



consecutiveFailures = 0 // Сбросить счетчик ошибок

return response, nil

}

}