Повторить асинхронные функции

В этом документе описывается, как можно запросить асинхронные (не HTTPS) фоновые функции для повторной попытки в случае сбоя.

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

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

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

  • Функция содержит ошибку, и среда выполнения выдает исключение.
  • Функция не может достичь конечной точки службы или при попытке сделать это истекает время ожидания.
  • Функция намеренно выдает исключение (например, когда параметр не проходит проверку).
  • Функция Node.js возвращает отклоненное обещание или передает null значение в функцию обратного вызова.

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

Семантика повтора

Cloud Functions обеспечивает по крайней мере одно выполнение событийно-управляемой функции для каждого события, выдаваемого источником событий. Способ настройки повторных попыток зависит от того, как вы создали свою функцию:

  • Функции, созданные в консоли Google Cloud или с помощью API Cloud Run Admin, требуют отдельного создания и управления триггерами событий. Триггеры имеют стандартные поведения повторных попыток, которые вы можете настроить в соответствии с потребностями вашей функции.
  • Функции, созданные с помощью API Cloud Functions v2, неявно создают необходимые триггеры событий, например, темы Pub/Sub или триггеры Eventarc . По умолчанию повторные попытки отключены для этих триггеров и могут быть повторно включены с помощью API Cloud Functions v2.

Функции, управляемые событиями, созданные с помощью Cloud Run

Функции, созданные в консоли Google Cloud или с помощью API Cloud Run Admin, требуют отдельного создания и управления триггерами событий. Мы настоятельно рекомендуем вам ознакомиться с поведением по умолчанию каждого типа триггера:

Функции, управляемые событиями, созданные с помощью API Cloud Functions v2

Функции, созданные с помощью API Cloud Functions v2 ; например, с помощью gcloud CLI , API REST или Terraform, будут создавать и управлять триггерами событий от вашего имени. По умолчанию, если вызов функции завершается с ошибкой, функция не вызывается снова, а событие отбрасывается. Когда вы включаете повторные попытки для функции, управляемой событиями, Cloud Functions повторяет неудачный вызов функции до тех пор, пока он не завершится успешно или не истечет окно повторных попыток.

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

Включить или отключить повторные попытки

Настройте повторные попытки из вашего кода функции

С помощью Cloud Functions for Firebase вы можете включить повторные попытки в коде функции. Чтобы сделать это для фоновой функции, такой как functions.foo.onBar(myHandler); , используйте runWith и настройте политику отказа:

functions.runWith({failurePolicy: true}).foo.onBar(myHandler);

Установка true , как показано, настраивает функцию на повторную попытку в случае сбоя.

Окно повтора

Для функций 2-го поколения это окно повтора истекает через 24 часа. Для функций 1-го поколения оно истекает через 7 дней. Cloud Functions повторяет вновь созданные функции, управляемые событиями, используя экспоненциальную стратегию отсрочки с увеличивающейся отсрочкой от 10 до 600 секунд. Эта политика применяется к новым функциям при первом их развертывании. Она не применяется ретроспективно к существующим функциям, которые были впервые развернуты до того, как изменения, описанные в этой заметке о выпуске, вступили в силу, даже если вы повторно развертываете функции.

Лучшие практики

В этом разделе описываются лучшие практики использования повторных попыток.

Используйте повтор для обработки временных ошибок

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

Установите конечное условие, чтобы избежать бесконечных циклов повторных попыток

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

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

Например, этот фрагмент кода отбрасывает все события старше 10 секунд:

const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
  console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
  callback();
  return;
}

Используйте catch с Promises

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

Вот пример того, что вам следует сделать:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

Сделать повторяющиеся функции, управляемые событиями, идемпотентными

Функции, управляемые событиями, которые можно повторить, должны быть идемпотентными. Вот некоторые общие рекомендации по созданию идемпотентной функции:

  • Многие внешние API (например, Stripe) позволяют вам указать ключ идемпотентности в качестве параметра. Если вы используете такой API, вам следует использовать идентификатор события в качестве ключа идемпотентности.
  • Идемпотентность хорошо работает с по крайней мере однократной доставкой, поскольку она делает повторные попытки безопасными. Поэтому общая лучшая практика для написания надежного кода — это сочетание идемпотентности с повторными попытками.
  • Убедитесь, что ваш код внутренне идемпотентен. Например:
    • Убедитесь, что мутации могут происходить более одного раза без изменения результата.
    • Запросите состояние базы данных в транзакции перед изменением состояния.
    • Убедитесь, что все побочные эффекты сами по себе идемпотентны.
  • Назначьте транзакционную проверку вне функции, независимо от кода. Например, сохраните состояние где-нибудь, записав, что данный идентификатор события уже был обработан.
  • Разбирайтесь с дублирующими вызовами функций вне полосы пропускания. Например, используйте отдельный процесс очистки, который выполняет очистку после дублирующих вызовов функций.

Настройте политику повторных попыток

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

  • Сократите интервал повторных попыток с 7 дней до 10 минут.
  • Измените минимальное и максимальное время отсрочки для стратегии повторения экспоненциальной отсрочки.
  • Измените стратегию повтора, чтобы повторить попытку немедленно.
  • Настройте тему «мертвых писем» .
  • Установите максимальное и минимальное количество попыток доставки.

Чтобы настроить политику повторных попыток:

  1. Напишите HTTP-функцию.
  2. Используйте API Pub/Sub для создания подписки Pub/Sub , указав URL-адрес функции в качестве цели.

Дополнительную информацию о настройке Pub Pub/Sub напрямую см. в документации Pub/Sub по обработке сбоев .