После деградации downstream-сервиса клиент начинает многократно повторять синхронный запрос. Какой механизм предотвращает усиление отказа?
Нужен ограниченный механизм повторов: короткий тайм-аут, небольшой лимит попыток, экспоненциальная задержка с jitter и быстрый отказ через circuit breaker при устойчивой деградации. Повторы должны применяться только к операциям, для которых безопасна повторная отправка, либо иметь защиту от дублирования.
Цель не в том, чтобы любой ценой дождаться ответа, а в том, чтобы сохранить ограниченную нагрузку и быстро сообщить вызывающей стороне о временной недоступности.
Синхронные интеграции естественным образом побуждают клиента повторить запрос, если не получен ответ. Это особенно заметно в распределённых системах, где тайм-аут может означать как потерю запроса, так и потерю только ответа.
Без специальных ограничений независимые клиенты начинают повторять вызовы одновременно. Так возникла необходимость в паттернах устойчивости: тайм-аутах, ограниченных retry, backoff, jitter и circuit breaker.
Пусть downstream-сервис обрабатывает запрос дольше обычного. Клиент считает вызов неуспешным по тайм-ауту и немедленно отправляет его снова. Если таких клиентов много, каждый новый повтор увеличивает очередь и конкуренцию за ресурсы, поэтому downstream отвечает ещё медленнее.
Возникает retry storm — лавина повторных запросов. Она может распространить исходный сбой на вызывающие сервисы и занять их рабочие потоки, соединения или лимиты. Для операций изменения данных дополнительный риск состоит в повторном выполнении действия, если первый запрос был принят, но его ответ потерялся.
Сначала задают тайм-аут на каждом исходящем вызове. Он должен учитывать допустимое время бизнес-операции, но не быть бесконечным; общий бюджет времени запроса распределяют между всеми downstream-вызовами.
Повторы ограничивают по числу попыток и общему времени. Между попытками используют экспоненциальную задержку, а случайную добавку (jitter) применяют, чтобы множество клиентов не повторяло запрос синхронно.
Повторять следует только ошибки, которые потенциально временные: например, временную перегрузку или сетевой сбой. Ошибки валидации, авторизации и другие явно постоянные ошибки повторять бессмысленно.
Для команд, изменяющих состояние, одной настройки retry недостаточно. Нужна идемпотентность операции или иной механизм дедупликации: после потери ответа повтор не должен приводить к повторному списанию, созданию второго заказа или другой дублирующей побочной операции.
Circuit breaker отслеживает долю или количество неудачных вызовов. В закрытом состоянии вызовы проходят, при превышении порога схема размыкается и временно отклоняет новые обращения без вызова неисправного сервиса. После паузы выполняются ограниченные пробные вызовы; при восстановлении схема закрывается, при новом отказе остаётся разомкнутой.
Circuit breaker не исправляет downstream-сервис и не заменяет тайм-аут. Он уменьшает нагрузку во время отказа и освобождает ресурсы вызывающей стороны. Цена решения — временные ошибки для пользователей даже в ситуации, когда отдельный запрос мог бы успешно завершиться, поэтому пороги, длительность открытого состояния и fallback требуют измерений и настройки.
Поведение должно быть согласовано на уровне цепочки. Если каждый из пяти сервисов делает по три повтора, один пользовательский запрос потенциально превращается в гораздо большее число вызовов. Обычно retry оставляют на одном контролируемом уровне, а ниже используют тайм-ауты и отказ без независимого многократного повторения.
Сервис оформления заказа вызывает сервис расчёта доставки. Во время неполадки расчёт занимает 8 секунд, а тайм-аут клиента равен 2 секундам. Первый вариант — немедленно повторять запрос до пяти раз. Он сохраняет шанс получить результат, но создаёт до пяти нагрузок на уже перегруженный сервис, увеличивает задержку и может дублировать операцию, если расчёт имеет побочные эффекты.
Второй вариант — полностью запретить повторы. Это защищает downstream от лавины, но делает кратковременные сетевые сбои видимыми пользователю и может ухудшить успешность безопасных запросов чтения.
Выбран вариант с общим бюджетом в 3 секунды: максимум одна повторная попытка только для временной ошибки, задержка с jitter, circuit breaker после серии неудач. Расчёт доставки не изменяет состояние, поэтому такой retry безопаснее, а при разомкнутом breaker заказ переводится в состояние ожидания расчёта вместо удержания рабочего потока.
В результате сервис перестал накапливать длинные синхронные очереди, а пользователь получил предсказуемый ответ о принятии заказа. Компромисс — часть расчётов стала завершаться асинхронно, поскольку устойчивость оказалась важнее гарантии немедленного ответа.
Нет. Тайм-аут означает отсутствие ответа у клиента, но не доказывает, что операция не была выполнена. Запрос мог успешно завершиться на сервере, а ответ — потеряться в сети. Для изменения состояния повтор допустим только при идемпотентном контракте, дедупликации по ключу операции или иной гарантии, предотвращающей повторный эффект.
Экспоненциальная задержка увеличивает интервалы между попытками, но одинаковые клиенты, стартовавшие примерно одновременно, могут повторить вызов примерно одновременно. Jitter добавляет случайное смещение и распределяет повторы во времени. Это снижает синхронные пики нагрузки, особенно после массового восстановления или краткого сбоя.
Он только ограничивает поток вызовов и изменяет поведение клиента при наблюдаемых ошибках. Breaker может ошибочно открыться из-за локальной сетевой проблемы или, наоборот, не сработать при медленном исчерпании ресурсов, если метрики настроены плохо. Нужны корректные тайм-ауты, наблюдаемость, контролируемые пробные вызовы и понятный fallback; сам breaker не устраняет причину отказа.