Что такое паттерн 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
}