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% маршрутів мають таймаути і документовану політику ретраїв/СВ.
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).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.