- Оригинальный адрес:Retries, Timeouts and Backoff
- Оригинальный автор:namc
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:nettee
- Корректор:fireairforce
Повтор, время ожидания и отсрочка
Распределенные системы сложны. Несмотря на то, что мы многое узнали о построении систем высокой доступности, отказоустойчивость при проектировании систем часто упускается из виду.
Мы определенно слышали об отказоустойчивости, но что такое «устойчивость»? Лично мне нравится определять это как способность системы справляться с непредвиденными ситуациями и в конечном итоге восстанавливаться после них. Есть много способов сделать вашу систему устойчивой к сбоям, но в этой статье мы сосредоточимся на следующем:
тайм-аут
Проще говоря, тайм-аут — это максимальное время бездействия между двумя последовательными пакетами.
Предположим, что в какой-то момент мы использовали драйверы базы данных и HTTP-клиенты. Все клиенты или драйверы, которые помогают вашей службе подключаться к внешнему серверу, имеют параметр Timeout. Этот параметр обычно по умолчанию равен нулю или -1, что означает, что время ожидания не определено или бесконечно.
Например: ссылкаconnectTimeout
иsocketTimeout
ОпределениеКонфигурация коннектора Mysql
Большинство запросов к внешним серверам имеют тайм-аут. Настройка таймаута очень нужна, когда внешний сервер вовремя не отвечает. Если вы не установите тайм-аут и используете значения по умолчанию 0/-1, ваша программа может заблокироваться на несколько минут или более. Это связано с тем, что когда вы не получаете ответ от сервера приложений, а ваш тайм-аут бесконечен или очень велик, соединение всегда открыто. Чем больше запросов поступает, тем больше соединений открывается и никогда не может быть закрыто. Это может привести к истощению пула подключений, что, в свою очередь, может привести к сбою приложения.
Затем, всякий раз, когда вы настраиваете свое приложение с помощью такого соединителя, обязательно задайте явное значение тайм-аута в конфигурации.
Тайм-аут должен быть реализован как во внешнем, так и в бэкэнде. Если операция чтения/записи слишком долго блокируется в REST API или интерфейсе сокета, она должна вызвать исключение и отключиться. Это информирует серверную часть об отмене операции и закрытии соединения, предотвращая постоянное открытие соединения.
Повторить попытку
нам может понадобиться знатьВременный сбойЭтот термин, потому что мы будем часто использовать его позже. Проще говоря, временный сбой в службе — это временный сбой, такой как перегрузка сети, перегрузка базы данных, который может восстановиться сам по себе после достаточного периода охлаждения.
Как определить, является ли неисправность мгновенной?
Ответ зависит от деталей реализации вашего ответа API/сервера. Если у вас есть REST API, верните503 Service Unavailable, вместо других кодов ошибок 5xx/4xx. Это позволяет клиенту узнать, что время ожидания было вызвано «временной перегрузкой», а не ошибкой на уровне кода.
Повторная попытка, хотя и полезная, может раздражать, если не настроена правильно. Ниже объясняется, как определить правильный метод повторной попытки.
Повторить попытку
Если ошибка, полученная от сервера, носит временный характер, например, сетевой пакет поврежден при передаче, приложение может немедленно повторить запрос, так как повторение ошибки маловероятно.
Однако такой подход весьма радикален. Такой подход может нанести ущерб вашему сервису, если он уже работает на полную мощность или полностью недоступен. Этот подход также замедляет время отклика вашего приложения, так как ваша служба продолжает пытаться выполнить операцию, завершившуюся сбоем.
Если ваша бизнес-логика требует такой стратегии повторных попыток, вам лучше ограничить количество повторных попыток и не отправлять слишком много запросов к одному и тому же источнику.
повторить попытку с задержкой
Если сбой вызван сбоем подключения или чрезмерным трафиком в сети, приложение должно добавить задержку перед повторной попыткой запроса на основе бизнес-логики.
for(int attempts = 0; attempts < 5; attempts++)
{
try
{
DoWork();
break;
}
catch { }
Thread.Sleep(50); // 延迟
}
При использовании библиотеки, которая подключается к внешней службе, убедитесь, что она реализует политику повторных попыток, позволяющую настроить максимальное количество повторных попыток, задержку между повторными попытками и т. д.
Вы также можете установитьRetry-AfterЗаголовок ответа реализует стратегию повтора на стороне сервера.
Также важно указать причину, по которой операция не удалась. Иногда операции завершаются сбоем из-за нехватки ресурсов, что можно устранить, добавив дополнительные экземпляры службы. Иногда операция завершается со сбоем из-за утечки памяти или исключения нулевого указателя. Что ж, важно добавить журналы для отслеживания поведения вашего приложения.
отвали
Как упоминалось выше, мы можем добавить задержку к стратегии повтора. Эту задержку часто называютЛинейная отсрочка. Возможно, это не лучший способ реализации стратегии повторных попыток.
Рассмотрим такую ситуацию: ваш сервис выходит из строя из-за перегрузки базы данных. Наш запрос, скорее всего, будет успешным после нескольких попыток. Но постоянная отправка запросов также можетусугублятьВаш сервер базы данных перегружен. В результате служба базы данных будет дольше оставаться в перегруженном состоянии, и потребуется больше времени для восстановления из перегруженного состояния.
Есть несколько стратегий, которые можно использовать для решения этой проблемы.
Как следует из названия, экспоненциальная отсрочка — это не периодическая задержка (например, 5 секунд) между повторными попытками, а экспоненциальное увеличение времени задержки. Повторные попытки будут продолжаться до максимального количества раз. Если запрос всегда терпит неудачу, сообщите клиенту, что запрос не удался.
Также необходимо установить ограничение на максимальное время задержки. Экспоненциальная отсрочка может привести к очень большим временам задержки, в результате чего запрошенный сокет остается открытым на неопределенный срок и переводит поток в спящий режим «навсегда». Это истощает системные ресурсы и вызывает больше проблем с пулом соединений.
int delay = 50
for(int attempts = 0; attempts < 5; attempts++)
{
try
{
DoWork();
break;
}
catch { }
Thread.sleep(delay);
if (delay < MAX_DELAY) // MAX_DELAY 可能依赖于应用程序和业务逻辑
{
delay *= 2;
}
}
Основным недостатком экспоненциальной отсрочки в распределенных системах является то, чтоЗапросы, которые начинают отсрочку в одно и то же время, также будут повторяться в то же время.. Это приводит к возникновению кластеров запросов. Что ж, вместо того, чтобы сокращать количество клиентов, конкурирующих в каждом раунде, мы вводим периоды, когда нет конкуренции между клиентами. Фиксированная экспоненциальная отсрочка не снижает количество конфликтов и создаетпик нагрузки.
Чтобы справиться с проблемой экспоненциальной отсрочки пиковой нагрузки, мы добавляем к стратегии отсрочкидрожание. Джиттер — это стратегия декорреляции, которая добавляет случайность в интервал между повторными попытками, чтобы распределить нагрузку и избежать кластеров сетевых запросов.
Джиттер, как правило, не является свойством конфигурации и должен быть реализован клиентом. Все, что нужно для джиттера, — это функция, которая может добавлять случайность для динамического расчета времени ожидания перед повторной попыткой.
После введения джиттера первоначальный набор неудачных запросов может быть сгруппирован в маленьком окне, скажем, 100 мс. Но после каждого повторного цикла кластер запросов расширяется во все более и более крупные временные окна. Когда запросы распределены по достаточно большому окну, служба, скорее всего, сможет их обработать.
int delay = 50
for(int attempts = 0; attempts < 5; attempts++)
{
try
{
DoWork();
break;
}
catch { }
Thread.sleep(delay);
delay *= random.randrange(0, min(MAX_DELAY, delay * 2 ** i)) // 只是生成一个简单的随机数
}
существуетдлительный кратковременный отказВ этом случае любая повторная попытка может быть не лучшим подходом. Такой сбой может быть вызван неудачным подключением, отключением электроэнергии (да, вполне реальная ситуация). Клиенты в конечном итоге повторяют попытку несколько раз, расходуя системные ресурсы и вызывая еще больше сбоев в системе.
Затем нам нужен механизм, который может определить, будет ли сбой сохраняться в долгосрочной перспективе, и реализовать решение для этой ситуации.
3. выключатель
Шаблон автоматического выключателя полезен при длительных кратковременных отказах служб. Он предотвращает повторные запросы клиентов, которые обречены на неудачу, определяя доступность службы.
Шаблон проектирования автоматического выключателя требует, чтобы состояние соединения сохранялось для серии запросов. покажи намавтоматический выключатель, реализованный отказоустойчивым
CircuitBreaker breaker = new CircuitBreaker()
.withFailureThreshold(5)
.withSuccessThreshold(3)
.withDelay(1, TimeUnit.MINUTES);
Failsafe.with(breaker).run(() -> connect());
Когда все работает нормально, неисправность отсутствует, и автоматический выключатель остается замкнутым.
При достижении порога возникновения неисправности автоматический выключатель срабатывает и входит вОткрытымгосударство. Это означает, что все последующие запросы будут терпеть неудачу напрямую без повторной логики.
После задержки (например, 1 минута, как указано выше) автоматический выключательПолуоткрытыйсостояние, проверьте, сохраняется ли проблема с запросом сети, и решите, должен ли автоматический выключатель быть замкнут или разомкнут. Если запрос выполнен успешно, автоматический выключатель сбрасывается взакрытиесостояние, в противном случае сбросить наОткрытымгосударство.
Это помогает избежать агрегации повторных запусков во время длительных сбоев, экономя системные ресурсы.
Хотя автоматические выключатели можно обслуживать локально с помощью переменной состояния. но если у вас естьРаспределенные системы, вам может понадобиться внешний уровень хранения. В конфигурации с несколькими узлами состояние сервера приложений должно быть общим для нескольких экземпляров. В этом сценарии вы можете использовать Redis, memcached для регистрации доступности внешних сервисов. Запрашивайте состояние службы из постоянного хранилища перед отправкой каких-либо запросов во внешнюю службу.
Идемпотентность в распределенных системах
Идемпотентная служба означает, что клиент может повторно инициировать один и тот же запрос и получить один и тот же конечный результат. Хотя сервер выдаст тот же результат для этой операции, клиент не обязательно будет реагировать так же.
Для REST API вам нужно помнить -
- POST нетИдемпотент — POST вызывает создание нового ресурса на сервере. n POST-запросы создают n новых ресурсов на сервере.
- GET,HEAD,OPTIONSиTRACEметоднавсегдаНе изменяет состояние ресурса на сервере. Следовательно, они всегда идемпотентны.
- PUTЗапросы идемпотентны. n Запросы PUT охватывают один и тот же ресурс n-1 раз.
- DELETEявляется идемпотентным, потому что сначала он вернет 200 (ОК), а последующие вызовы вернут 204 (Нет содержимого) или 404 (Не найдено).
Зачем сосредотачиваться на идемпотентных операциях?
В распределенной системе есть несколько серверных и клиентских узлов. Если вы отправляете запрос от клиента на сервер A, и запрос завершается ошибкой или истекает время ожидания, вы хотите иметь возможность просто отправить этот запрос еще раз, не беспокоясь о том, имел ли предыдущий запрос какие-либо побочные эффекты.
Это чрезвычайно важно для микросервисов, поскольку многие компоненты работают независимо друг от друга.
Некоторые из основных преимуществ идемпотентности:
- минимальная сложность- Не нужно беспокоиться о побочных эффектах, вы можете просто повторить любой запрос с тем же конечным результатом.
- Легко реализовать- Вам не нужно добавлять логику для обработки ранее неудачных запросов в вашем механизме повторных попыток.
- легко проверить- Каждое действие дает один и тот же результат, никаких сюрпризов.
Эпилог
Мы рассмотрели ряд способов создания более отказоустойчивых систем. Однако эти методы не все. Наконец, я хотел бы указать на несколько моментов, которые вы должны рассмотреть, чтобы улучшить доступность и отказоустойчивость вашей системы.
- В конфигурации с несколькими узлами, если клиент повторяет попытку несколько раз, эти запросы, скорее всего, достигнут одного и того же сервера. На этом этапе лучше вернуть неудачный ответ и позволить клиенту повторить попытку с самого начала.
- Ведите статистику производительности своих систем и держите их готовыми к худшему. вы можете просмотретьОбезьяна Хаоса от Netflix- Это инструмент тестирования устойчивости, который вызывает случайные сбои в системе. Это позволяет подготовиться к возможным сбоям и построить устойчивую систему.
- Если ваша система по какой-то причине перегружена, вы можете попробовать распределить нагрузку с помощью сброса нагрузки. Гугл отлично поработалтематическое исследование, может быть хорошей отправной точкой.
Некоторые ресурсы:
- Шаблоны в распределенных системах
- Режим повторной попытки — Майкрософт
- Мартин Фаулер - Автоматический выключатель
благодарный! ❤
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.