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

Сети и протоколы

Retry-бури, circuit breakers и load shedding

Суть Автоматические retry усиливают нагрузку в 2^K раз через K слоёв микросервисов; circuit breakers останавливают каскад быстрыми 503; retry-бюджеты и экспоненциальный откат с джиттером — обязательные production-меры.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

У backend GC-пауза 500 мс. Сто клиентов истекают по таймауту. Каждый делает retry. Теперь у backend в очереди 200 запросов к уже приостановленному backend. Retry от retry накапливаются. За секунды сбой 500 мс превращается в полный сбой кластера. Это retry-буря — и автоматические retry её вызвали.

Проблема retry-усиления

Backend даёт сбой (GC-пауза, исчерпание thread pool, кратковременная перегрузка). Запросы истекают по таймауту. Клиенты и LB делают retry. Retry добавляет нагрузку на уже нарушенный backend. Backend ещё больше отстаёт. Накапливается ещё больше retry.

Математика усиления. С N=100 клиентами и 1 retry каждый: 100 оригинальных запросов + 100 retry = 200 запросов на backend, который не мог обработать 100. При 2 retry каждый: 300. При K=10 слоях микросервисов с 1 retry на ошибку, усиление в худшем случае достигает 2^10 = 1024 раз от исходной нагрузки.

Это не теоретическая проблема. Это наиболее частая причина продолжительных сбоев кластера в архитектурах микросервисов.

Почему retry ухудшают, а не улучшают ситуацию. Retry правильны для преходящих сбоев (кратковременная потеря пакетов, мгновенный сбой DNS). Они катастрофичны для перегрузочных сбоев: retry в перегруженный backend добавляет именно ту нагрузку, что вызвала перегрузку. Backend не может разгрузить очередь, потому что retry её постоянно пополняют.

Circuit breakers

Circuit breaker стоит на уровне LB или клиента сервиса и отслеживает частоту отказов для каждого backend. Три состояния:

  1. Closed (нормальное): Запросы проходят. Счётчик отказов отслеживается.
  2. Open: После N последовательных отказов (или превышения порога частоты отказов) breaker открывается. Все новые запросы получают 503 Service Unavailable немедленно — без очереди, без ожидания.
  3. Half-open: После периода остывания один запрос пропускается. Если он успешен, breaker закрывается. Если нет — вновь открывается и период остывания сбрасывается.

Почему быстрый 503 помогает. Когда breaker открыт, клиенты получают 503 немедленно вместо ожидания таймаута (30 с). Они могут перейти к fallback, retry другого сервиса или сбросить запрос. Перегруженный backend не получает новой нагрузки — он может разгрузить существующую очередь и восстановиться.

Конфигурация circuit breaking Envoy:

  • max_connections: максимум одновременных TCP-соединений к backend (например, 1 000). 1 001-й запрос: 503 немедленно.
  • max_pending_requests: максимум запросов в очереди при занятости всех соединений (например, 100). Избыток: 503 немедленно.
  • max_requests: максимум одновременных HTTP/2-запросов на соединение (например, 1 000).
Усиление retry-бури
100 клиентов × 1 retry
200 запросов к тонущему backend
100 клиентов × 2 retry
300 запросов
K=10 слоёв × 1 retry каждый
усиление до 1 024 раз
Безопасная частота retry (production SLO)
<0,1% от частоты запросов
Circuit breaker открыт → клиент видит
503 немедленно (без ожидания таймаута)
Диапазон джиттера (base откат 1 с)
случайная задержка 0–1 с

Retry-бюджеты

Вместо неограниченных retry установите глобальный retry-бюджет: ограничьте общее число retry от всех клиентов до ~10% от общей частоты запросов.

Пример: если сервис обрабатывает 10 000 RPS, разрешить не более 1 000 RPS retry. Любой retry сверх этого возвращает 503 немедленно (fail fast). Это предотвращает превышение усиления ограниченного коэффициента независимо от числа одновременно делающих retry клиентов.

Экспоненциальный откат с джиттером

Клиенты не должны делать retry немедленно — это синхронизирует все retry и создаёт thundering herd. Два компонента:

  1. Экспоненциальный откат: Задержка retry удваивается при каждой попытке: 1 с → 2 с → 4 с → 8 с → … до максимума (например, 32 с).
  2. Джиттер: Добавить случайное значение random(0, base) к каждой задержке. Это рассинхронизирует клиентов, чтобы они делали retry в разное время.
delay = min(base × 2^attempt + random(0, base), max_delay)

Пример с base=1 с, max=32 с:

  • Попытка 1: 1 с + rand(0, 1 с).
  • Попытка 2: 2 с + rand(0, 2 с).
  • Попытка 3: 4 с + rand(0, 4 с).

Без джиттера все 100 клиентов, истёкших по таймауту в t=30 с, делают retry в t=31 с, создавая новый всплеск. С джиттером они делают retry в разное время в диапазоне t=31–32 с.

Load shedding

LB и backend оба принудительно ограничивают глубину очереди. Когда очередь превышает порог, новые запросы немедленно сбрасываются с 503 вместо принятия и постановки в очередь.

Без load shedding:

  • Очередь растёт безгранично.
  • Задержка каждого запроса в очереди увеличивается.
  • Память раздувается.
  • В итоге всё истекает по таймауту одновременно.

С load shedding:

  • Клиенты получают быстрый 503 — знают, что нужно отступить.
  • Очередь backend остаётся ограниченной.
  • Запросы, попавшие в очередь, обрабатываются за конечное время.
  • Система разгружается и восстанавливается вместо коллапса.
Проследи
1/5

Проследите каскадный отказ: перегрузка backend, retry-буря, срабатывание circuit breaker.

1
Step 1 of 5
Backend B1 испытывает исчерпание thread pool. Запросы становятся в очередь. Активный health check (HTTP GET) всё ещё успешен (endpoint доступен). Что видят LB и клиенты?
2
Locked
При 100 клиентах, каждый делает 1 retry, нагрузка на B1 возрастает со 100 до скольки запросов? Что происходит с очередью B1?
3
Locked
B1 исключён. Его 200 запросов из очереди теперь перераспределяются на B2, B3, B4. Каков риск?
4
Locked
LB имеет circuit breaker: максимум 100 одновременных запросов на backend. B2 достигает предела. Что видят новые запросы?
5
Locked
Thread pool B1 восстанавливается через 2 минуты. Как он должен вернуться в пул?
Найди ошибку

Дамп статистики Envoy во время retry-бури

log
cluster.api_backend.requests: 10000
cluster.api_backend.errors: 245
cluster.api_backend.retries: 189
cluster.api_backend_retry_limit: 1200
upstream_rq_retry.api_backend: 189
upstream_rq_retry_limit_exceeded: 45
upstream_rq_total.api_backend: 10000
upstream_cx: 156
health_checks.failed: 12
health_checks.success: 188
circuit_breaker.default.rq_open: 0
circuit_breaker.default.cx_open: 0
outlier_detection.ejected_count: 0
load_balancer.least_request.unbalanced_requests_delta: 234

Статистика показывает 189 retry из 10 000 запросов (~1,9% частота retry) и 45 запросов достигли предела retry. Кластер здоров? Что должен делать оператор?

Граничные случаи

Бюджет retry на слой vs глобальный. В стеке микросервисов, если каждый из 10 слоёв разрешает 1 retry, усиление в худшем случае равно 2^10. Правильная архитектура: выделить общий retry-бюджет на самом внешнем слое (API gateway или клиент) и распространять заголовок retry-remaining внутрь. Внутренние сервисы вообще не делают retry, если только заголовок не предоставляет им бюджет. Это ограничивает усиление до 2 раз независимо от глубины стека.

Вспомните перед уходом
  1. 01
    Объясните проблему retry-усиления в архитектуре микросервисов с N слоями. Почему частота retry 1% на слой катастрофична?
  2. 02
    Как circuit breaker останавливает retry-бурю и каковы его три состояния?
  3. 03
    Почему нужно добавлять джиттер к экспоненциальному откату и что он предотвращает?
Итог

Retry-бури — наиболее частая причина продолжительных аварий микросервисов. Когда backend даёт сбой, автоматические retry добавляют нагрузку к уже нарушенному backend — усиленную в 2^K раз через K слоёв сервисов. Circuit breakers останавливают каскад, возвращая 503 немедленно после N отказов, давая backend пространство для разгрузки и восстановления. Минимальные production-меры: retry-бюджеты, ограничивающие retry до <10% от RPS; экспоненциальный откат с джиттером для рассинхронизации времени retry; load shedding для сброса новых запросов при превышении глубины очереди. Алертинг при частоте retry выше 0,1% — это самый ранний предупреждающий знак до эскалации бури.

Связанные уроки
встречается в258
Продолжить восхождение ↑Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observability
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.