Логика повтора выполнена правильно: реализация экспоненциального увеличения задержки для надежных систем

В разработке программного обеспечения надежная логика повторных попыток важна для обработки временных сбоев, таких как проблемы с сетью или временные отключения. Недавно я наткнулся на кодовую базу, где разработчик использовал цикл for с фиксированным интервалом времени для повторных попыток неудачных операций. Хотя такой подход может показаться простым, ему не хватает устойчивости, необходимой для реальных приложений. Здесь на помощь приходит экспоненциальная задержка — стратегия, разработанная для того, чтобы сделать повторные попытки более умными и эффективными.

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

Экспоненциальная задержка — это стратегия повторных попыток, при которой время ожидания между попытками увеличивается экспоненциально после каждого сбоя. Вместо повторных попыток с фиксированными интервалами каждая последующая попытка ждет дольше, чем предыдущая — обычно время задержки удваивается каждый раз. Например, если начальная задержка составляет 1 секунду, следующие попытки будут происходить через 2, 4, 8 секунд и так далее. Такой подход помогает снизить нагрузку на систему и минимизировать риск перегрузки внешних служб в периоды высокого спроса.

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

  • Сниженная нагрузка на систему: Разделяя повторные попытки, экспоненциальная задержка минимизирует вероятность перегрузки серверов, что особенно полезно для обработки ограничений по количеству запросов или временных сбоев.

  • Эффективная обработка ошибок: Увеличивающаяся задержка предоставляет временные проблемы больше времени для естественного разрешения, повышая вероятность успешной повторной попытки.

  • Улучшенная стабильность: Особенно для систем с высоким трафиком, это предотвращает поток повторных попыток, позволяя приложениям работать без перебоев и без чрезмерного потребления ресурсов.

  • Увеличенная задержка: Каждая повторная попытка занимает все больше времени, поэтому экспоненциальная задержка может привести к задержкам, особенно если требуется много повторных попыток перед успехом.

Экспоненциальная задержка особенно полезна в сценариях, где системы взаимодействуют с внешними сервисами или обрабатывают большие объемы трафика. Вот некоторые другие распространенные случаи использования:

  1. API с ограничением по количеству запросов: Некоторые API имеют ограничения по количеству запросов, ограничивая их в течение определенного времени. Экспоненциальный откат помогает избежать немедленных повторных попыток, которые могут превысить лимит, давая время для его сброса.

  2. Нестабильность сети: В случаях временных сбоев сети или тайм-аутов экспоненциальный откат помогает, ожидая дольше между попытками, позволяя сети стабилизироваться.

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

  4. Системы очередей: В системах очередей сообщений, если сообщение не удается обработать из-за ошибки, использование экспоненциального отката для повторных попыток может предотвратить быстрое переработку и дать время для разрешения временных проблем.

Чтобы продемонстрировать экспоненциальный откат, мы создадим базовый сервис отправки электронной почты, который будет повторно пытаться отправить письма в случае возникновения ошибки. Этот пример показывает, как экспоненциальный откат улучшает процесс повторных попыток по сравнению с простым циклом for.

import nodemailer from "nodemailer";
import { config } from "../common/config";
import SMTPTransport from "nodemailer/lib/smtp-transport";

const emailSender = async (
  subject: string,
  recipient: string,
  body: string
): Promise<boolean> => {
  const transport = nodemailer.createTransport({
    host: config.EMAIL_HOST,
    port: config.EMAIL_PORT,
    secure: true,
    auth: { user: config.EMAIL_SENDER, pass: config.EMAIL_PASSWORD },
  } as SMTPTransport.Options);

  const mailOptions: any = {
    from: config.EMAIL_SENDER,
    to: recipient,
    subject: subject,
  };

  const maxRetries = 5; // maximum number of retries before giving up
  let retryCount = 0;
  let delay = 1000; // initial delay of 1 second

  while (retryCount < maxRetries) {
    try {
      // send email
      await transport.sendMail(mailOptions);
      return true;
    } catch (error) {
      // Exponential backoff strategy
      retryCount++;
      if (retryCount < maxRetries) {
        const jitter = Math.random() * 1000; // random jitter(in seconds) to prevent thundering herd problem
        const delayMultiplier = 2
        const backOffDelay = delay * delayMultiplier ** retryCount + jitter;
        await new Promise((resolve) => setTimeout(resolve, backOffDelay));
      } else {
        // Log error
        console.log(error)
        return false; // maximum number of retries reached
      }
    }
  }
  return false;
};

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

  1. Начальная задержка
  • Цель: Устанавливает время ожидания перед первой повторной попыткой. Оно должно быть достаточно длинным, чтобы предотвратить немедленные повторные попытки, но достаточно коротким, чтобы избежать заметных задержек.

  • Рекомендуемая настройка: Начните с задержки от 500 мс до 1000 мс. Для критических систем используйте более короткую задержку, в то время как менее срочные операции могут иметь более длительную задержку.

  1. Множитель задержки
  • Назначение: Управляет тем, как быстро увеличивается задержка после каждой попытки повторения. Множитель 2 удваивает задержку (например, 1с, 2с, 4с).

  • Рекомендуемая настройка: Обычно множитель в диапазоне 1.5 и 2 обеспечивает баланс между отзывчивостью и стабильностью. Более высокие множители (например, 3) могут быть подходящими, если система может выдерживать более длительные задержки между попытками повторения.

  1. Максимальное количество попыток
  • Назначение: Ограничивает количество попыток повторения, чтобы предотвратить чрезмерные попытки, которые могут истощить ресурсы или увеличить нагрузку на систему.

  • Рекомендуемая настройка: Обычно достаточно диапазона 3 до 5 попыток для большинства приложений. За пределами этого операция может быть зарегистрирована как неудачная или управляться иначе, например, уведомлением пользователя или срабатыванием оповещения.

  1. Джиттер (рандомизация)
  • Цель: Добавляет случайность к каждому задержке, чтобы предотвратить сгруппирование повторных попыток и избежать эффекта «громадного стада».
  • Рекомендуемая настройка: Добавьте случайную задержку между 0 и 500 мс к каждому интервалу повторной попытки. Этот джиттер помогает более равномерно распределить попытки повторения во времени.

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

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

Счастливого кодирования! 👨‍💻❤️

Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems