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

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

Устойчивость: каскадные повторы, circuit breakers и error budget

Суть Нескоординированные повторы на всех двенадцати уровнях превращают медленный сервис в общесайтовый отказ. Circuit breakers, exponential backoff с jitter, bulkheads и error budget — многоуровневая защита от каскадов.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 16 min

Сторонний платёжный API замедляется до 5 секунд на ответ. Ваш сервер ждёт, получает таймаут, возвращает 503. Клиентская библиотека повторяет через 100 мс, 200 мс, 400 мс. Так делают ещё 1 000 пользователей. Ваш origin-сервер, обычно обрабатывающий 500 req/s, теперь получает 3 000 req/s — и всё ещё ждёт медленный платёжный API. Через 30 секунд весь сайт недоступен. Платёжный API был медленным, а не сломанным. Но вы превратили «медленно» в «катастрофу», повторяя запросы без координации.

Анатомия каскада

Retry storm — это положительная обратная связь: один таймаут → много повторов → больше нагрузки → больше таймаутов → больше повторов.

Математика усиления. Origin-сервер с 99,9% доступностью (1 ошибка на 1 000 запросов) в норме справляется. Во время замедления запросы скапливаются в очереди. 1 000 клиентов каждый повторяет 3 раза с наивным backoff. Трафик к origin: 3× нормы. Origin замедляется больше. Больше повторов. Через 30 секунд origin получает 10× нормального трафика. Сервер с 99,9% доступностью теперь узкое место для 100% трафика. Один деградировавший зависимый сервис превращает 0,1% ошибок в 100% отказ.

Проблема двенадцати уровней. Каждый уровень в цепочке запросов имеет свою логику повторов:

  • DNS-клиент повторяет при SERVFAIL (3 попытки, каждые 2 с).
  • TCP SYN повторяет при таймауте (экспоненциальный backoff, до 3 попыток).
  • TLS handshake повторяет при таймауте (1–2 попытки).
  • HTTP-клиентская библиотека повторяет при 5xx (3 попытки, экспоненциальный backoff по умолчанию).
  • Повтор на уровне приложения (бизнес-логика повторяет неудачные вызовы сервисов).

Все двенадцать уровней повторяют независимо. Единственный сбой на уровне 6 (обработка origin) вызывает повторы на уровнях 3–12 одновременно. В совокупности один неудачный запрос становится 30+ сетевыми операциями — все они усугубляют нагрузку на и без того перегруженный origin.

ЗащитаЧто предотвращаетСтоимость
Exponential backoff + jitterСинхронизированный thundering herd при повторахМедленнее восстановление для отдельных клиентов
Circuit breakerПовторы к отказывающему зависимому сервисуБыстрые ошибки при открытом circuit; нужна настройка
BulkheadГолодание всех потоков из-за одного медленного сервисаПул потоков на зависимость; сложность
Load sheddingТаймауты из-за глубины очередиБыстрые ошибки 503 для избыточного трафика
Graceful degradationПолный отказ при недоступности originУстаревшие данные; сложный дизайн кэша

Защита 1: Exponential backoff с jitter

Наивный повтор. Клиент видит 503, повторяет немедленно. Сервер всё ещё сломан. Клиент видит 503, повторяет снова. Сервер никогда не восстанавливается.

Экспоненциальный backoff. Первый повтор через 100 мс, второй через 200 мс, третий через 400 мс, четвёртый через 800 мс (удвоение каждый раз, ограниченное максимумом). Origin получает время для восстановления между повторами.

Почему jitter необходим. Если 1 000 клиентов все достигают предела и все используют одни параметры backoff, они все повторяют в t=100 мс, t=200 мс и т.д. одновременно. Origin получает всплеск каждые 100 мс — thundering herd. Jitter: добавить ±25% случайной вариации к каждой задержке повтора. 1 000 клиентов распределяют повторы по окну 200 мс. Нагрузка на origin постоянная, а не импульсная.

Формула полного jitter (Jeff Atwood / Amazon): sleep = random_between(0, min(cap, base * 2^attempt)). Это даёт до cap мс сна на последней попытке, полностью рандомизированный — наиболее эффективный против thundering herd.

Защита 2: Circuit breaker

Паттерн из электротехники: переключатель, который размыкается при превышении тока порога, предотвращая каскадный ущерб.

Три состояния:

  1. Closed (замкнутый) (норма). Запросы проходят. Мониторится частота ошибок и задержка.
  2. Open (разомкнутый) (сработавший). Частота ошибок или задержка превысила порог для последних N запросов. Новые запросы немедленно отказывают (быстрая ошибка, без сетевого вызова). Сервер не опрашивается.
  3. Half-open (полуоткрытый) (зонд). После окна остывания (например, 30 с) один запрос пропускается. Если успешен, circuit закрывается (норма). Если неуспешен, circuit снова размыкается.

Почему быстрый отказ помогает. Если circuit открыт и возвращает 503 за <1 мс, клиент завершается быстро. С exponential backoff + jitter клиент ждёт и повторяет. Origin получает облегчение. Когда origin восстанавливается, полуоткрытый зонд успешен и трафик возобновляется. Без circuit breaker клиент продолжает повторять секундами — добавляя нагрузку на и без того перегруженный origin.

Реализация. Библиотеки: Netflix Hystrix (Java, deprecated), Resilience4j (Java), Polly (.NET), opossum (Node.js). Конфигурация: порог отказов (например, 50% ошибок за последние 10 запросов), остывание half-open (30 с), размер скользящего окна.

Защита 3: Bulkhead

Назван по аналогии с переборками корабля, изолирующими затопление в одной секции. В ПО: давать каждому зависимому сервису отдельный пул потоков (или async семафор). Если платёжный API медленный, он заполняет пул потоков платежей. Пулы потоков user API, product API и CDN-fetch остаются доступными. Медленный зависимый сервис не голодит все потоки.

Без bulkheads. Общий пул потоков из 100. Платёжный API замедляется → 100 запросов в очереди → все 100 потоков заняты → каждый API endpoint (user, product, search) заблокирован. Сайт недоступен для всех функций, а не только для платежей.

С bulkheads. Платежи: 20 потоков. Пользователи: 30 потоков. Продукты: 30 потоков. Прочее: 20 потоков. Платёжный API замедляется → 20 потоков платежей заняты → платёжный endpoint медленный → все остальные endpoints не затронуты. Graceful частичная деградация.

Защита 4: Load shedding

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

Почему быстрый 503 лучше таймаута. Запрос, в итоге завершившийся таймаутом через 30 с, удерживает поток, соединение с БД и память 30 с. Запрос, получивший 503 за <1 мс, немедленно освобождает все ресурсы. Клиент может повторить с backoff. Сервер остаётся стабильным.

Реализация. Nginx: limit_req_zone + limit_req (на основе скорости, возвращает 503 при заполнении очереди). В приложении: проверять глубину очереди запросов; если > порога, возвращать 503 немедленно. Сочетать с stale-while-revalidate на CDN, чтобы кэшированный контент всё ещё обслуживался с edge при перегрузке origin.

Error budget и SLO-ориентированный release cadence

SLO (Service Level Objective). SLO на 99,9% доступности за 30-дневное окно означает, что сервис может иметь 0,1% ошибок — около 43,2 минут простоя. SLO 99,95% допускает ~21,6 минут.

Error budget. Допустимое время отказов. Когда бюджет сжигается:

  • При 50% использовании: замедлить рискованные изменения (релизы функций, изменения конфигурации).
  • При 100% использовании: заморозить все несущественные релизы до улучшения надёжности.

Это формализует компромисс задержка/скорость выпуска без политики. Инженеры могут выпускать быстро, пока есть бюджет; эксплуатация может применять заморозку релизов, когда бюджет исчерпан.

Performance budgets. Распространить ту же идею на загрузку страницы: установить явные цели (LCP < 2,5 с, бандл JS < 200 КБ). Применять в CI инструментами типа bundlewatch, size-limit, Lighthouse CI. Сборка, превышающая бюджет, падает в pipeline. Заставляет делать осознанные компромиссы при PR вместо обнаружения раздутости в продакшне.

Почему это работает

Error budget разрешает классическое противоречие надёжность-скорость. Вместо споров инженерной команды и продукта «можно ли выпустить это рискованное изменение», error budget отвечает математически: если у вас осталось 30 минут бюджета в этом месяце, изменение с 20% вероятностью инцидента на 5 минут стоит риска (ожидаемая стоимость: 1 минута); изменение с 20% вероятностью инцидента на 45 минут — нет (ожидаемая стоимость: 9 минут > бюджет 30 минут). Количественный риск заменяет мнение.

Проследи
1/5

Каскадный retry storm от DDoS-атаки с усилением: как единственный отказ на уровне 11 каскадирует через все двенадцать уровней.

1
Step 1 of 5
Уровень 11 (Network Security): Ботнет запускает объёмный DDoS 1 Гбит/с (UDP flood) на origin. Как реагирует rate limiter?
2
Locked
Уровень 10 (QUIC): Атака переключается на усиление с поддельными IP. Ограничение QUIC против усиления?
3
Locked
Уровень 9 (Proxy/Load Balancing): Таймаут у легитимных клиентов. Проверки здоровья балансировщика нагрузки не проходят. Что происходит?
4
Locked
Уровни 3 до 1 (TCP до Physical): Перегрузка распространяется назад. Сигнатура отказа?
5
Locked
Многоуровневый ответ: circuit breaker + load shedding + jitter + graceful degradation.
Выбери лучший вариант

Продукт для совместной работы в реальном времени требует p95 задержки менее 100 мс глобально для редактирования документов.

Спроектируй

Спроектируйте архитектуру пути запросов для глобального SaaS, который должен соответствовать: p95 загрузка страницы 1 с, p95 API-вызов 200 мс, доступность 99,99%.

  • Глобальная пользовательская база, преимущественно Северная Америка + Европа + Азия.
  • Преимущественно read-трафик; часть write-трафика для аккаунтов пользователей и правок документов.
  • Требования compliance: данные пользователей ЕС должны оставаться в ЕС.
Викторина

Каков error budget для SLO доступности 99,95% за 30-дневное окно?

Викторина

Circuit breaker находится в состоянии OPEN. Приходит новый запрос. Что должно произойти?

Вспомните перед уходом
  1. 01
    Объясните математику усиления: как сервер с 99,9% доступностью полностью падает при retry storm?
  2. 02
    Почему добавление jitter к exponential backoff предотвращает thundering herd и какова формула полного jitter?
  3. 03
    Старший архитектор утверждает, что edge compute всегда лучше региональных origin. Где ошибка в этом аргументе?
Итог

Каскадные retry storm возникают потому, что каждый уровень в двенадцатифазной цепочке запросов имеет независимую логику повторов — когда зависимый сервис замедляется, все уровни повторяют одновременно, усиливая трафик с 1× до 10× менее чем за минуту. Стек защиты: exponential backoff с полным jitter (распределяет повторы), circuit breakers (быстрая ошибка при отказе, half-open зонд для восстановления), bulkheads (пул потоков на зависимость для сдерживания радиуса поражения) и load shedding (быстрый 503 при превышении глубины очереди). Error budget формализует компромисс скорость/надёжность: SLO 99,95% допускает 21,6 минут простоя за 30-дневное окно; исчерпание бюджета вызывает заморозку релизов, а не спор. Полная архитектура для глобального SaaS сочетает CDN edge для статики, региональные Kubernetes-кластеры с региональным хранением данных, OpenTelemetry с tail-based семплированием ошибок и CI-применяемые performance budgets — рассматривая каждый уровень как потенциальный домен отказа с явными runbook.

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

Trademarks belong to their respective owners. Editorial reference only.