awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

Circuit breakers: чтение кода и конфига

Суть Читай реальный конфиг breaker'а и код места вызова, предсказывай переходы состояний и поведение срабатывания и выбирай фикс, который senior сделает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Поведение breaker’а решается в его конфиге и в месте вызова. Прочитай каждый сниппет, предскажи, что breaker реально делает под нагрузкой, и выбери изменение, которое senior сделает первым.

Цель

Отработай цикл, который ты гоняешь на каждом ревью breaker’а: прочитай конфиг и обёртку вызова, предскажи переходы состояний и поведение срабатывания и заметь мисконфигурацию, из-за которой breaker срабатывает не так — или не срабатывает вовсе.

Сниппет 1 — машина состояний

// resilience4j: проводим один breaker через инцидент
CircuitBreaker cb = registry.circuitBreaker("payments");

// 1. closed: вызовы проходят, отказы считаются
cb.executeSupplier(() -> paymentClient.charge(req));   // ok
cb.executeSupplier(() -> paymentClient.charge(req));   // throws -> counted

// ... failure rate переходит failureRateThreshold ...
// breaker переходит: CLOSED -> OPEN, стартует waitDurationInOpenState

cb.executeSupplier(() -> paymentClient.charge(req));   // <- что тут?
Викторина

Breaker только что перешёл CLOSED в OPEN, и таймер cooldown идёт. Приходит следующий вызов executeSupplier. Что происходит и какова нагрузка на зависимость прямо сейчас?

Сниппет 2 — конфиг порога

CircuitBreakerConfig.custom()
    .slidingWindowType(SlidingWindowType.COUNT_BASED)
    .slidingWindowSize(100)
    .failureRateThreshold(50)        // срабатывать при 50% отказов
    .minimumNumberOfCalls(100)       // ...но только после 100 вызовов
    .build();
// эндпоинт: /admin/report  ->  ~6 запросов в минуту
Викторина

Этот конфиг применён к низконагруженному admin-эндпоинту, обслуживающему примерно 6 запросов/минуту. Зависимость за ним жёстко падает. Как ведёт себя breaker и почему?

Сниппет 3 — half-open проба

CircuitBreakerConfig.custom()
    .waitDurationInOpenState(Duration.ofSeconds(10))
    .permittedNumberOfCallsInHalfOpenState(10)
    // automaticTransitionFromOpenToHalfOpenEnabled = false (default)
    .build();
// зависимость восстановилась на секунде 4; breaker сработал на секунде 0
// между секундой 0 и секундой 30 вызовы не приходят
Викторина

Зависимость восстановилась на секунде 4, а cooldown — 10 с, но между секундой 0 и секундой 30 вызовы не приходят. Когда этот breaker реально зондирует и снова открывается для трафика?

Сниппет 4 — взаимодействие с timeout

// breaker считает отказы, но у вызова нет бюджета времени
Supplier<Resp> guarded = CircuitBreaker
    .decorateSupplier(cb, () -> recsClient.fetch(req));  // может висеть 30с

// во время инцидента recsClient.fetch не ошибается -- он просто висит
Resp r = guarded.get();
Викторина

Во время инцидента recsClient.fetch перестаёт ошибаться и вместо этого висит по 30 с на вызов. Breaker подключён, но никогда не срабатывает. Какой единственный фикс с наибольшим рычагом?

Итог

Поведение breaker’а читается в конфиге и в месте вызова. OPEN короткозамыкает каждый вызов мгновенно, так что зависимость получает нулевую нагрузку; минимальный порог объёма, рассчитанный на высокий трафик, тихо блокирует срабатывание на низконагруженном эндпоинте (используй меньший порог или time-based окно); с отключённым авто-переходом breaker зондирует только когда вызов приходит после cooldown, никогда по одному таймеру, поэтому простаивающий breaker остаётся открытым, пока не вернётся трафик; и breaker без timeout слеп к зависанию, потому что только бюджет времени превращает зависание в считаемый отказ. Диагностируй по конфигу и обёртке, затем чини ту одну настройку, что заставляет breaker срабатывать правильно.

Продолжить восхождение ↑Circuit breakers: сдержи каскадный сбой
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.