Circuit Breaker это защитный паттерн для распределённых систем. Он автоматически «отключает» вызовы к нестабильному сервису, чтобы предотвратить каскадные сбои и снизить нагрузку на и без того падающий узел.
Аналогия проста: как автоматический выключатель в электрощитке защищает проводку от перегрузки, так и Circuit Breaker защищает систему от цепной реакции ошибок.
Два состояния
Замкнуто — штатный режим. Все запросы проходят в обычном режиме. Счётчик ошибок растёт при каждом сбое.
Разомкнуто — защитный режим. После превышения порога ошибок паттерн перестаёт вызывать сервис и сразу возвращает ошибку. Сервис получает время на восстановление.
Реализация:
type Circuit func(context.Context) (string, error)
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 {
// Экспоненциальная выдержка: 2, 4, 8... секунд
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 {
consecutiveFailures++
return response, err
}
consecutiveFailures = 0 // Успех — сбрасываем счётчик
return response, nil
}
}
Что важно в этой реализации
•
sync.RWMutex защищает общее состояние при конкурентных вызовах• Экспоненциальная выдержка (
2 << d) даёт сервису всё больше времени на восстановление с каждой неудачной попыткой• После успешного вызова счётчик сбрасывается — цепь «замыкается» обратно автоматически
• Функция возвращает тот же тип
Circuit, что позволяет прозрачно встраивать Breaker без изменения кода клиента🐸 Библиотека Go для собеса