GH GambleHub

Circuit Breaker и ретраи

Circuit Breaker и ретраи

1) Зачем это нужно

Сети ненадежны: латентность пульсирует, узлы падают, лимиты достигаются. Ретраи спасают от кратковременных сбоев, а Circuit Breaker защищает систему от каскадных отказов и «само-DDoS». Комбинация с правильными таймаутами и лимитами сохраняет SLO, стабилизирует хвостовые задержки и цену «девяток».

2) Базовые принципы

Сначала таймауты, потом ретраи, затем Circuit Breaker.
Ретраим только идемпотентные операции (GET, безопасные POST/PUT с идемпотентным ключом).
Выделяйте бюджет ретраев: ≤ 10–15% от исходного RPS на маршрут.
Локализуйте отказ: bulkhead (раздельные пулы/квоты) + rate-limit.
При деградации — быстрый отказ (fail-fast), graceful-degradation/заглушки.

3) Семантика ретраев

Когда ретраить

Транзиентные ошибки: timeouts, 5xx, сетевая недоступность, 429 (после `Retry-After`).
Нельзя ретраить: явные бизнес-ошибки (4xx ≠ 429), side-effects без идемпотентности (платеж без ключа).

Стратегии

Exponential backoff + jitter (полный или равномерный): сглаживает стаи ретраев.
Max attempts: 1–2 (редко 3) — больше обычно вредно.
Budget: глобальный счетчик ретраев/сек на сервис и per-request «retry tokens».
Hedging (редко): параллельный дубль запроса после t-квантили (p95) — только для строго идемпотентных чтений.

Псевдокод backoff+джиттер:
python base = 100 # ms for attempt in range(1, max_attempts+1):
try:
return call()
except Transient as e:
if attempt == max_attempts: raise sleep_ms = min(cap_ms, base 2(attempt-1))
sleep(random(0, sleep_ms)) # full jitter

4) Таймауты и «быстрый отказ»

Client timeout < upstream timeout: чтобы не копить «зомби» запросы.
Делите: connect timeout, read timeout, overall deadline.
Tail-aware таймауты: целимся в p95/p99 + небольшой запас.
Используйте общее дедлайн-поле (например, gRPC `deadline`) и пробрасывайте его вниз по цепочке.

5) Circuit Breaker: как работает

Состояния:
  • Closed: пропускает трафик, считает ошибки/латентность.
  • Open: сразу отдает быстрый отказ (или запасной ответ).
  • Half-Open: проверочные запросы; при успехе — закрывается.
Порог открытия:
  • Ошибки/таймауты превышают долю X% за окно N запросов/секунд или p99 выше порога.
  • Релевантна скользящая статистика и минимальный объем (например, ≥ 50 запросов).

6) Bulkhead, квоты и «разделяй и властвуй»

Раздельные пуллы соединений per-upstream и per-фича.
Квоты на in-flight запросы; лишнее — быстрый отказ.
При нехватке — деградация фич с низким приоритетом (feature flags).

7) Интеграция с периметром (Envoy/Istio/Nginx)

Envoy (retry + outlier + CB, идея):
yaml routes:
- match: { prefix: "/api" }
route:
cluster: upstream_api timeout: 2s retry_policy:
retry_on: "connect-failure,reset,retriable-4xx,5xx"
num_retries: 2 per_try_timeout: 600ms retry_back_off: { base_interval: 100ms, max_interval: 800ms }
hedge_policy:
hedge_on_per_try_timeout: true initial_requests: 1 additional_request_chance: { numerator: 5, denominator: HUNDRED } # 5%
clusters:
- name: upstream_api circuit_breakers:
thresholds:
- priority: DEFAULT max_connections: 500 max_requests: 1000 max_retries: 200 outlier_detection:
consecutive_5xx: 5 interval: 5s base_ejection_time: 30s max_ejection_percent: 50
Istio (VirtualService fault/retry, сжатый пример):
yaml apiVersion: networking. istio. io/v1beta1 kind: VirtualService spec:
hosts: ["payments"]
http:
- route: [{ destination: { host: payments } }]
timeout: 2s retries:
attempts: 2 perTryTimeout: 600ms retryOn: "5xx,connect-failure,refused-stream,reset"
Nginx Ingress (аннотации):
yaml nginx. ingress. kubernetes. io/proxy-connect-timeout: "2"
nginx. ingress. kubernetes. io/proxy-read-timeout: "2"
nginx. ingress. kubernetes. io/proxy-next-upstream: "error timeout http_502 http_503 http_504"
nginx. ingress. kubernetes. io/proxy-next-upstream-tries: "2"

8) Библиотеки и код (стек-сниппеты)

Java (Resilience4j):
java var cb = CircuitBreaker. ofDefaults("psp");
var retry = Retry. of("psp-retry",
RetryConfig. custom()
.maxAttempts(2)
.waitDuration(Duration. ofMillis(200))
.intervalFunction(IntervalFunction. ofExponentialRandomBackoff(100, 2. 0, 0. 5) )//jitter
.retryExceptions(SocketTimeoutException. class, IOException. class)
.build());

Supplier<Response> decorated =
CircuitBreaker. decorateSupplier(cb,
Retry. decorateSupplier(retry, () -> client. call()));

return Try. ofSupplier(decorated)
.recover(BusinessException. class, fallback())
.get();
Go (context deadline + backoff):
go ctx, cancel:= context. WithTimeout(context. Background(), 2time. Second)
defer cancel()
var lastErr error for i:= 0; i < 2; i++ {
reqCtx, stop:= context. WithTimeout(ctx, 600time. Millisecond)
lastErr = call(reqCtx)
stop()
if lastErr == nil { break }
sleep:= time. Duration(rand. Intn(1<<uint(7+i))) time. Millisecond // full jitter time. Sleep(min(sleep, 800time. Millisecond))
}
if lastErr!= nil { return fastFail() }
Node.js (got + p-retry):
js import pRetry from 'p-retry';
await pRetry(() => got(url, { timeout: { connect: 500, request: 2000 } }), {
retries: 2,
factor: 2,
randomize: true,
minTimeout: 100,
maxTimeout: 800,
onFailedAttempt: e => { if (isBusiness(e)) throw e; }
});

9) Бюджет ретраев и SLO

Введите retry tokens: каждый ретрай тратит токен; пул лимитирован.
Свяжите с error-budget: при burn-rate выше порога — отключаем ретраи, открываем CB чаще, включаем деградацию.
Канареечные релизы: на канареях уменьшите попытки и токены.

10) Hedging (осторожно)

Запускайте дополнительный запрос после п95-дедлайна, отменяя проигравший.
Только для чтений и «безопасных» идемпотентных операций; лимитируйте долю (≤ 1–5%).
Следите за ростом нагрузки на апстрим.

11) Наблюдаемость

RED-метрики по маршрутам: Rate, Error, Duration (p50/p95/p99).
CB-метрики: состояние (open/half-open), частота открытий, пропущенные/отказанные запросы.
Ретраи: attempts/request, retry-rate, сожженные токены.
Периметр: outlier-ejection, ejection-rate.
Трейсы: аннотируйте `retry_attempt`, `cb_state`, `hedged=true`, пробрасывайте `trace_id`.

12) Интеграция с архитектурой

Bulkhead + CB на каждый критичный апстрим.
Очереди/асинхрон: для долгих операций вместо безумных таймаутов.
Кеш/заглушки: для не-критичных фич при fail-open.
Автоскейл: не компенсирует плохие ретраи — сначала остановите «бурю».

13) Анти-паттерны

Ретраи без таймаутов → «зависшие» коннекты и истощение пулов.
Повтор неидемпотентных операций (двойные списания).
Бесконечный экспоненциальный рост без cap и джиттера.
Единый CB на все апстримы → перетаскивание отказа на весь продукт.
Игнорирование 429/`Retry-After`.
Таймаут клиента дольше, чем у апстрима (или вовсе нет).
«Лечить» бизнес-ошибки ретраями.

14) Чек-лист внедрения (0–30 дней)

0–7 дней

Определите маршруты и их идемпотентность.
Задайте таймауты (connect/read/overall), включите минимальные ретраи (×1) и CB по умолчанию.
Разделите пулы/квоты (bulkhead) для основных апстримов.

8–20 дней

Включите джиттер и глобальный бюджет ретраев, алерты по retry-rate.
Настройте outlier-ejection на периметре, быстрый отказ для low-prio фич.
Дашборды RED + CB/Retry, трейсы с метками.

21–30 дней

Канареечные профили ретраев (меньше попыток), game-day «апстрим медленный/флапает».
Документируйте политику: кто/что ретраит, пределы, исключения.
Пересмотрите p95/p99 и таймауты по данным, а не на глаз.

15) Метрики зрелости

100% маршрутов имеют таймауты и документированную политику ретраев/CB.
Retry-rate укладывается в бюджет (≤ 10–15%), нет всплесков при инцидентах.
CB срабатывают раньше, чем падает весь пул; нет каскадных отказов.
Трейсы показывают попытки/hedging; p99 стабилен при пиках.
Канареечные релизы используют «бережный» профиль ретраев.

16) Короткие примеры конфигураций

Resilience4j YAML (Spring Boot, идея):
yaml resilience4j:
circuitbreaker:
instances:
psp:
slidingWindowType: COUNT_BASED slidingWindowSize: 100 minimumNumberOfCalls: 50 failureRateThreshold: 50 waitDurationInOpenState: 30s permittedNumberOfCallsInHalfOpenState: 5 retry:
instances:
psp:
maxAttempts: 2 waitDuration: 200ms enableExponentialBackoff: true exponentialBackoffMultiplier: 2. 0 retryExceptions:
- java. net. SocketTimeoutException
- java. io. IOException
Envoy rate-limit (фрагмент идеи):
yaml rate_limits:
- actions:
- generic_key: { descriptor_value: "api. payments" }

17) Заключение

Устойчивость — это дисциплина: таймауты → ретраи (с джиттером и бюджетом) → Circuit Breaker + bulkhead/квоты и быстрый отказ. Настройте периметр (outlier-ejection), повесьте дашборды RED/CB/Retry, зафиксируйте политику идемпотентности и не забывайте про бизнес-SLI. Тогда краткие сбои останутся незаметными, а настоящие инциденты не превратятся в каскадные падения.

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Telegram
@Gamble_GC
Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.