Что такое паттерн Circuit Breaker и в каких случаях вы его применяли?

Ответ

Да, я применял паттерн Circuit Breaker (Предохранитель) в микросервисной архитектуре для повышения отказоустойчивости системы.

Что это?

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

Ключевые состояния:

  • Closed (Замкнут): Изначальное состояние. Запросы свободно проходят к сервису. Если количество сбоев превышает порог, предохранитель переходит в состояние Open.
  • Open (Разомкнут): Запросы к сервису немедленно блокируются, клиенту возвращается ошибка. По истечении тайм-аута предохранитель переходит в состояние Half-Open.
  • Half-Open (Полуоткрыт): Пропускается один тестовый запрос. Если он успешен, предохранитель возвращается в Closed. Если нет — снова в Open.

Когда использовать?

Я использовал его при взаимодействии между микросервисами, особенно для вызовов критически важных, но потенциально нестабильных зависимостей:

  • Вызовы внешних API (платежные шлюзы, сервисы геолокации).
  • Запросы к перегруженной базе данных или кэшу.
  • Синхронное общение между внутренними сервисами.

Пример с библиотекой sony/gobreaker:

import (
    "fmt"
    "net/http"
    "time"
    "github.com/sony/gobreaker"
)

// Создаем Circuit Breaker с настройками
var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name:        "HTTP-GET",
    MaxRequests: 1, // Количество запросов в состоянии Half-Open
    Interval:    0, // Период сброса счетчиков (0 - не сбрасывать)
    Timeout:     10 * time.Second, // Период, после которого состояние меняется с Open на Half-Open
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        // Переходим в Open после 3 последовательных сбоев
        return counts.ConsecutiveFailures > 3
    },
})

func makeRequest() (interface{}, error) {
    // Защищаем вызов внешнего сервиса
    body, err := cb.Execute(func() (interface{}, error) {
        resp, err := http.Get("http://example.com/api")
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        if resp.StatusCode >= 500 {
            return nil, fmt.Errorf("service returned error: %s", resp.Status)
        }
        // ... обработка успешного ответа
        return []byte("OK"), nil
    })

    if err != nil {
        // Если ошибка - это разрыв цепи, обрабатываем ее
        if err == gobreaker.ErrOpenState {
            fmt.Println("Circuit Breaker is open. Service is unavailable.")
        }
        return nil, err
    }

    return body, nil
}

Ответ 18+ 🔞

Ах ты ж ёпта, паттерн Circuit Breaker! Да это же как раз та самая штука, которая в микросервисах от каскадного пиздеца спасает. Ну, представь, один сервис лёг, а к нему другие как давай стучаться, да стучаться, пока все вместе не накрылись медным тазом. А этот предохранитель — он как умный мужик на проходной: видит, что в цеху дым идёт, и просто говорит «всё, братва, проход закрыт, иди отсюда, пока не взорвалось».

Ну и что это за зверь такой?

Circuit Breaker — это паттерн, который не даёт тупо долбить по уже мёртвому сервису. Работает точь-в-точь как автомат в щитке: наебнулось несколько раз подряд — хлоп, и цепь разорвана. Дальше все запросы летят в пизду мгновенно, без попыток. Даёт системе передохнуть, а проблемному сервису — не умереть окончательно под лавиной запросов.

Состояния у него, блядь, как у человека:

  • Closed (Замкнут): Всё спокойно, трафик идёт как по маслу. Но если ошибок сверх порога — щёлк, и предохранитель в Open.
  • Open (Разомкнут): Полный игнор. Любой запрос получает по ебалу сразу, без диалога. «Сервис недоступен, иди нахуй». Через заданный таймаут переходит в Half-Open, типа проверим, очухался ли.
  • Half-Open (Полуоткрыт): Осторожно, как на минное поле, пропускаем один пробный запрос. Прошёл — ура, закрываем цепь. Снова наебнулся — ну всё, опять в Open, сиди дальше.

Где я это применял?

Да везде, где есть хоть тень сомнения в надёжности соседа! Особенно:

  • Вызовы к внешним API, которые могут в любой момент сказать «пошёл нахуй, я в отпуске».
  • Обращения к базе, которая уже еле дышит от нагрузки.
  • Любое синхронное общение между нашими же сервисами, когда один начинает тупить.

Вот, смотри, как на Go с библиотекой sony/gobreaker выглядит:

import (
    "fmt"
    "net/http"
    "time"
    "github.com/sony/gobreaker"
)

// Настраиваем нашего сторожа
var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name:        "HTTP-GET",
    MaxRequests: 1, // В состоянии Half-Open пропустит только один запрос на разведку
    Interval:    0, // Не сбрасываем счётчики со временем
    Timeout:     10 * time.Second, // Через 10 секунд после Open попробуем Half-Open
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        // Если три раза подряд обосрались — всё, концерт окончен, размыкаем цепь.
        return counts.ConsecutiveFailures > 3
    },
})

func makeRequest() (interface{}, error) {
    // Оборачиваем наш рискованный вызов
    body, err := cb.Execute(func() (interface{}, error) {
        resp, err := http.Get("http://example.com/api")
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        if resp.StatusCode >= 500 {
            // Сервер сам сказал, что он — манда с ушами
            return nil, fmt.Errorf("service returned error: %s", resp.Status)
        }
        // ... если всё ок, обрабатываем ответ
        return []byte("OK"), nil
    })

    if err != nil {
        // Если ошибка — это потому что цепь разомкнута
        if err == gobreaker.ErrOpenState {
            fmt.Println("Circuit Breaker is open. Service is unavailable.")
        }
        return nil, err
    }

    return body, nil
}

Вот и весь сказ. Просто, а эффективно, как удар кирпичом по голове. Система перестаёт героически пытаться достучаться до трупа и начинает жить своей жизнью, пока тот не очухается. Ёперный театр, а не паттерн!