Ретраи, таймауты и circuit breaker
Как сервисы переживают чужие сбои: таймауты, безопасные ретраи с backoff и circuit breaker против каскадных отказов.
Сеть ненадёжна
Сервис почти всегда зависит от других: базы, очереди, чужого API. Любой из них может ответить медленно или не ответить вовсе. Если сервис к этому не готов, один медленный сосед роняет всю систему. Три приёма делают сервис устойчивым: таймауты, ретраи, circuit breaker.
Таймаут: не ждать вечно
Таймаут ограничивает, сколько ждать ответа. Без него запрос к зависшей зависимости висит, держит соединение и поток, и копятся такие висящие запросы, пока сервис не упрётся в лимиты из урока про дескрипторы.
http_client.get("http://payments/charge", timeout=2s)
Таймаут ставят всегда и подбирают по реальному времени ответа. Слишком короткий рвёт нормальные запросы, слишком длинный не спасает от зависаний. Частая ошибка — запрос без таймаута: рано или поздно он повесит сервис.
Ретрай: повторить с умом
Ретрай повторяет запрос, который не удался из-за временной проблемы
(таймаут, 503, обрыв соединения). Но повторять надо осторожно, иначе
ретраи делают хуже.
Два правила:
- Не повторять неидемпотентное вслепую. Повтор
GETбезопасен. ПовторPOSTможет создать второй заказ. Что повторять безопасно, разбирает следующий урок про идемпотентность. - Backoff с jitter. Повторять не сразу и не с фиксированным интервалом,
а с растущей паузой (
exponential backoff: 1с, 2с, 4с) и случайной добавкой (jitter). Иначе тысяча клиентов повторят разом в одну секунду и добьют уже шатающийся сервис.
attempt 1 → fail → wait 1s + rand
attempt 2 → fail → wait 2s + rand
attempt 3 → fail → отдать ошибку вверх
Число попыток ограничивают: бесконечные ретраи — та же лавина.
Circuit breaker: перестать долбить
Если зависимость лежит, ретраи к ней бессмысленны и только тратят ресурсы. Circuit breaker (предохранитель) следит за долей ошибок. Когда она переваливает порог, он «размыкается»: на время перестаёт пускать запросы к этой зависимости и сразу отдаёт ошибку (или запасной ответ), не тратя таймаут на каждый вызов.
Через паузу breaker пропускает пробный запрос. Прошёл, значит зависимость ожила, и он «замыкается» обратно. Это спасает от каскада: лежащая база не тянет за собой все сервисы, которые в неё ходят.
Что важно запомнить
Таймаут ставят на любой внешний вызов: без него зависшая зависимость вешает сам сервис. Ретрай повторяет только временные ошибки, с растущей паузой и случайным разбросом, иначе клиенты добивают сервис синхронной лавиной.
Неидемпотентное (POST) вслепую не повторяют. Circuit breaker размыкается
при высокой доле ошибок и сразу отдаёт отказ, не давая лежащей зависимости
уронить всё остальное.
В следующем уроке разберём идемпотентность в API: как сделать повторы безопасными.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс