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

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

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

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

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

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

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

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

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

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

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

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

Настройте повторы из консоли GCP

Если вы создаете новую функцию:

  1. На экране «Создать функцию» выберите «Добавить триггер» и выберите тип события, которое будет служить триггером для вашей функции.
  2. На панели триггеров Eventarc установите флажок Повторить попытку при сбое, чтобы разрешить повторные попытки.

Если вы обновляете существующую функцию:

  1. На странице «Обзор облачных функций» щелкните имя функции, которую вы обновляете, чтобы открыть экран сведений о ее функции , затем выберите «Изменить» в строке меню, чтобы отобразить панели триггеров HTTPS и Eventarc .
  2. На панели триггеров Eventarc щелкните значок , чтобы изменить настройки триггера.
  3. На панели триггеров Eventarc установите или снимите флажок Повторить попытку при сбое, чтобы включить или отключить повторные попытки.

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

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

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

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

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

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

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

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

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

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

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

Например, этот фрагмент кода отбрасывает все события старше 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 с обещаниями

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

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

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/Sub см. в документации Pub/Sub по обработке сбоев .