← Паттерны приложений
12 мин · средне · Урок 4 из 5

Ретраи, таймауты и circuit breaker

Как сервисы переживают чужие сбои: таймауты, безопасные ретраи с backoff и circuit breaker против каскадных отказов.

Сеть ненадёжна

Сервис почти всегда зависит от других: базы, очереди, чужого API. Любой из них может ответить медленно или не ответить вовсе. Если сервис к этому не готов, один медленный сосед роняет всю систему. Три приёма делают сервис устойчивым: таймауты, ретраи, circuit breaker.

Таймаут: не ждать вечно

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

http_client.get("http://payments/charge", timeout=2s)

Таймаут ставят всегда и подбирают по реальному времени ответа. Слишком короткий рвёт нормальные запросы, слишком длинный не спасает от зависаний. Частая ошибка — запрос без таймаута: рано или поздно он повесит сервис.

Ретрай: повторить с умом

Ретрай повторяет запрос, который не удался из-за временной проблемы (таймаут, 503, обрыв соединения). Но повторять надо осторожно, иначе ретраи делают хуже.

Два правила:

attempt 1 → fail → wait 1s + rand
attempt 2 → fail → wait 2s + rand
attempt 3 → fail → отдать ошибку вверх

Число попыток ограничивают: бесконечные ретраи — та же лавина.

Circuit breaker: перестать долбить

Если зависимость лежит, ретраи к ней бессмысленны и только тратят ресурсы. Circuit breaker (предохранитель) следит за долей ошибок. Когда она переваливает порог, он «размыкается»: на время перестаёт пускать запросы к этой зависимости и сразу отдаёт ошибку (или запасной ответ), не тратя таймаут на каждый вызов.

доля ошибок выше порога через паузу пробный прошёл closedзапросы идут как обычно openсразу отказ, без ожидания half-openодин пробный запрос
Три состояния и переходы между ними: порог ошибок размыкает, пауза даёт пробный запрос, успех замыкает обратно.

Через паузу breaker пропускает пробный запрос. Прошёл, значит зависимость ожила, и он «замыкается» обратно. Это спасает от каскада: лежащая база не тянет за собой все сервисы, которые в неё ходят.

Что важно запомнить

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

Неидемпотентное (POST) вслепую не повторяют. Circuit breaker размыкается при высокой доле ошибок и сразу отдаёт отказ, не давая лежащей зависимости уронить всё остальное.

В следующем уроке разберём идемпотентность в API: как сделать повторы безопасными.

Прошли материал до конца? Отметьте урок пройденным.