Как работает паттерн «Circuit Breaker» (Предохранитель)?

Ответ

Паттерн Circuit Breaker (Предохранитель) — это механизм защиты, который предотвращает каскадные сбои в распределенных системах. Он отслеживает ошибки при вызове удаленного сервиса и, если их количество превышает порог, временно "разрывает цепь", прекращая дальнейшие вызовы и немедленно возвращая ошибку.

Состояния (States):

  1. Closed (Замкнуто): Начальное состояние. Запросы свободно проходят к удаленному сервису. Каждый сбой инкрементирует счетчик ошибок.
  2. Open (Разомкнуто): Если счетчик ошибок достигает заданного порога, "предохранитель" переходит в это состояние. Все последующие запросы немедленно отклоняются с ошибкой, без попытки вызова сервиса. Запускается таймер сброса.
  3. Half-Open (Полуоткрыто): По истечении таймера сброса "предохранитель" переходит в это состояние. Он пропускает один тестовый запрос к сервису. Если запрос успешен — цепь замыкается (переход в Closed). Если неудачен — цепь снова размыкается (возврат в Open), и таймер сброса запускается заново.

Пример потокобезопасной реализации на Go:

import (
    "errors"
    "sync"
    "time"
)

// Константы для состояний
const (
    StateClosed   = "closed"
    StateOpen     = "open"
    StateHalfOpen = "half-open"
)

type CircuitBreaker struct {
    mu              sync.Mutex
    state           string
    failures        int
    threshold       int           // Порог ошибок для размыкания
    resetTimeout    time.Duration // Таймаут перед переходом в Half-Open
    lastFailureTime time.Time
}

func (cb *CircuitBreaker) Call(f func() error) error {
    cb.mu.Lock()

    if cb.state == StateOpen {
        if time.Since(cb.lastFailureTime) > cb.resetTimeout {
            cb.state = StateHalfOpen
        } else {
            cb.mu.Unlock()
            return errors.New("circuit is open")
        }
    }

    cb.mu.Unlock()

    err := f()

    cb.mu.Lock()
    defer cb.mu.Unlock()

    if err != nil {
        cb.failures++
        if cb.state == StateHalfOpen || cb.failures >= cb.threshold {
            cb.state = StateOpen
            cb.lastFailureTime = time.Now()
        }
        return err
    }

    // При успехе сбрасываем счетчик и замыкаем цепь
    cb.failures = 0
    cb.state = StateClosed
    return nil
}

Преимущества:

  • Отказоустойчивость: Предотвращает выход из строя всей системы из-за одного неисправного компонента.
  • Быстрый отказ (Fast Fail): Не тратит ресурсы на заведомо провальные вызовы.
  • Graceful Degradation: Позволяет системе продолжать работать, пусть и с ограниченной функциональностью.