В этом документе описываются передовые методы проектирования, внедрения, тестирования и развертывания Cloud Functions .
Корректность
В этом разделе описываются общие рекомендации по проектированию и внедрению Cloud Functions .
Написать идемпотентные функции
Ваши функции должны выдавать тот же результат, даже если они вызываются несколько раз. Это позволяет вам повторить вызов, если предыдущий вызов не сработал на полпути в вашем коде. Для получения дополнительной информации см. retrying event-driven functions .
Не запускайте фоновые действия
Фоновая активность — это все, что происходит после завершения функции. Вызов функции завершается после возврата из функции или иным образом сигнализирует о завершении, например, путем вызова аргумента callback
в функциях Node.js, управляемых событиями. Любой код, запущенный после корректного завершения, не может получить доступ к процессору и не будет выполнять никаких действий.
Кроме того, когда последующий вызов выполняется в той же среде, ваша фоновая активность возобновляется, мешая новому вызову. Это может привести к неожиданному поведению и ошибкам, которые трудно диагностировать. Доступ к сети после завершения функции обычно приводит к сбросу соединений (код ошибки ECONNRESET
).
Фоновая активность часто может быть обнаружена в журналах отдельных вызовов, путем поиска всего, что регистрируется после строки, сообщающей о завершении вызова. Фоновая активность иногда может быть спрятана глубже в коде, особенно когда присутствуют асинхронные операции, такие как обратные вызовы или таймеры. Просмотрите свой код, чтобы убедиться, что все асинхронные операции завершены, прежде чем вы прекратите функцию.
Всегда удаляйте временные файлы
Локальное дисковое хранилище во временном каталоге — это файловая система в памяти. Файлы, которые вы записываете, потребляют память, доступную вашей функции, и иногда сохраняются между вызовами. Отсутствие явного удаления этих файлов может в конечном итоге привести к ошибке нехватки памяти и последующему холодному запуску.
Вы можете просмотреть объем памяти, используемый отдельной функцией, выбрав ее в списке функций в консоли Google Cloud и выбрав график использования памяти .
Если вам необходим доступ к долгосрочному хранилищу, рассмотрите возможность использования монтирования томов Cloud Run с томами Cloud Storage или NFS .
Вы можете уменьшить требования к памяти при обработке больших файлов с помощью конвейеризации. Например, вы можете обработать файл в Cloud Storage, создав поток чтения, пропустив его через потоковый процесс и записав выходной поток непосредственно в Cloud Storage.
Функции Фреймворка
Чтобы гарантировать, что одни и те же зависимости устанавливаются одинаково во всех средах, мы рекомендуем вам включить библиотеку Functions Framework в ваш менеджер пакетов и прикрепить зависимость к определенной версии Functions Framework.
Для этого включите предпочтительную версию в соответствующий файл блокировки (например, package-lock.json
для Node.js или requirements.txt
для Python).
Если Functions Framework явно не указан как зависимость, он будет автоматически добавлен в процессе сборки с использованием последней доступной версии.
Инструменты
В этом разделе приведены рекомендации по использованию инструментов для внедрения, тестирования и взаимодействия с Cloud Functions .
Местное развитие
Развертывание функции занимает некоторое время, поэтому часто быстрее протестировать код функции локально.
Разработчики Firebase могут использовать эмулятор Cloud Functions Firebase CLI .Избегайте тайм-аутов развертывания во время инициализации
Если развертывание функции завершается ошибкой тайм-аута, это, скорее всего, означает, что глобальный код области действия вашей функции выполняется слишком долго в процессе развертывания.
Firebase CLI имеет тайм-аут по умолчанию для обнаружения ваших функций во время развертывания. Если логика инициализации в исходном коде ваших функций (загрузка модулей, выполнение сетевых вызовов и т. д.) превышает этот тайм-аут, развертывание может завершиться неудачей.
Чтобы избежать тайм-аута, используйте одну из следующих стратегий:
(Рекомендуется) использовать onInit()
для отсрочки инициализации
Используйте хук onInit()
, чтобы избежать запуска кода инициализации во время развертывания. Код внутри хука onInit()
будет запущен только тогда, когда функция развернута в функциях Cloud Run, а не во время самого процесса развертывания.
Node.js
const { onInit } = require('firebase-functions/v2/core'); const { onRequest } = require('firebase-functions/v2/https'); // Example of a slow initialization task function slowInitialization() { // Simulate a long-running operation (e.g., loading a large model, network request). return new Promise(resolve => { setTimeout(() => { console.log("Slow initialization complete"); resolve("Initialized Value"); }, 20000); // Simulate a 20-second delay }); } let initializedValue; onInit(async () => { initializedValue = await slowInitialization(); }); exports.myFunction = onRequest((req, res) => { // Access the initialized value. It will be ready after the first invocation. res.send(`Value: ${initializedValue}`); });
Питон
from firebase_functions.core import init from firebase_functions import https_fn import time # Example of a slow initialization task def _slow_initialization(): time.sleep(20) # Simulate a 20-second delay print("Slow initialization complete") return "Initialized Value" _initialized_value = None @init def initialize(): global _initialized_value _initialized_value = _slow_initialization() @https_fn.on_request() def my_function(req: https_fn.Request) -> https_fn.Response: # Access the initialized value. It will be ready after the first invocation. return https_fn.Response(f"Value: {_initialized_value}")
(Альтернатива) Увеличьте время ожидания обнаружения
Если вы не можете реорганизовать свой код для использования onInit()
, вы можете увеличить время ожидания развертывания CLI с помощью переменной среды FUNCTIONS_DISCOVERY_TIMEOUT
:
$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions
Используйте Sendgrid для отправки писем
Cloud Functions не разрешает исходящие соединения на порту 25, поэтому вы не можете создавать незащищенные соединения с SMTP-сервером. Рекомендуемый способ отправки писем — использовать сторонний сервис, такой как SendGrid . Другие варианты отправки писем можно найти в руководстве Отправка писем из экземпляра для Google Compute Engine.
Производительность
В этом разделе описываются лучшие практики оптимизации производительности.
Избегайте низкого уровня параллелизма
Поскольку холодные запуски обходятся дорого, возможность повторного использования недавно запущенных экземпляров во время пика нагрузки является отличной оптимизацией для обработки нагрузки. Ограничение параллелизма ограничивает то, как существующие экземпляры могут быть использованы, тем самым вызывая больше холодных запусков.
Повышение параллелизма помогает отложить обработку нескольких запросов на экземпляр, что упрощает обработку пиков нагрузки.Используйте зависимости с умом
Поскольку функции не имеют состояния, среда выполнения часто инициализируется с нуля (во время так называемого холодного старта ). Когда происходит холодный старт, оценивается глобальный контекст функции.
Если ваши функции импортируют модули, время загрузки этих модулей может добавить задержку вызова во время холодного старта. Вы можете уменьшить эту задержку, а также время, необходимое для развертывания вашей функции, правильно загрузив зависимости и не загружая зависимости, которые ваша функция не использует.
Используйте глобальные переменные для повторного использования объектов в будущих вызовах.
Нет гарантии, что состояние функции будет сохранено для будущих вызовов. Однако Cloud Functions часто повторно использует среду выполнения предыдущего вызова. Если вы объявляете переменную в глобальной области видимости, ее значение можно повторно использовать в последующих вызовах без необходимости пересчета.
Таким образом, вы можете кэшировать объекты, которые могут быть дорогими для повторного создания при каждом вызове функции. Перемещение таких объектов из тела функции в глобальную область видимости может привести к значительному повышению производительности. Следующий пример создает тяжелый объект только один раз для каждого экземпляра функции и делится им между всеми вызовами функций, достигающими данного экземпляра:
Node.js
console.log('Global scope'); const perInstance = heavyComputation(); const functions = require('firebase-functions'); exports.function = functions.https.onRequest((req, res) => { console.log('Function invocation'); const perFunction = lightweightComputation(); res.send(`Per instance: ${perInstance}, per function: ${perFunction}`); });
Питон
import time from firebase_functions import https_fn # Placeholder def heavy_computation(): return time.time() # Placeholder def light_computation(): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start instance_var = heavy_computation() @https_fn.on_request() def scope_demo(request): # Per-function scope # This computation runs every time this function is called function_var = light_computation() return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
Эта HTTP-функция принимает объект запроса ( flask.Request
) и возвращает текст ответа или любой набор значений, которые можно преобразовать в объект Response
с помощью make_response
.
Особенно важно кэшировать сетевые соединения, библиотечные ссылки и объекты API-клиента в глобальном масштабе. См. примеры в разделе Оптимизация сети .
Уменьшите количество холодных запусков, установив минимальное количество экземпляров
По умолчанию Cloud Functions масштабирует количество экземпляров на основе количества входящих запросов. Вы можете изменить это поведение по умолчанию, установив минимальное количество экземпляров, которые Cloud Functions должны поддерживать в готовности для обслуживания запросов. Установка минимального количества экземпляров сокращает количество холодных запусков вашего приложения. Мы рекомендуем устанавливать минимальное количество экземпляров и завершать инициализацию во время загрузки, если ваше приложение чувствительно к задержкам.
Дополнительную информацию об этих параметрах времени выполнения см. в разделе Управление поведением масштабирования .Заметки о холодном запуске и инициализации
Глобальная инициализация происходит во время загрузки. Без нее первому запросу пришлось бы завершить инициализацию и загрузить модули, что привело бы к более высокой задержке.
Однако глобальная инициализация также влияет на холодные запуски. Чтобы минимизировать это влияние, инициализируйте только то, что необходимо для первого запроса, чтобы задержка первого запроса была как можно ниже.
Это особенно важно, если вы настроили min instances, как описано выше, для чувствительной к задержке функции. В этом сценарии завершение инициализации во время загрузки и кэширование полезных данных гарантирует, что первому запросу не нужно этого делать, и он обслуживается с низкой задержкой.
Если вы инициализируете переменные в глобальной области видимости, в зависимости от языка, длительное время инициализации может привести к двум вариантам поведения: - для некоторых комбинаций языков и асинхронных библиотек фреймворк функций может работать асинхронно и немедленно возвращаться, заставляя код продолжать выполняться в фоновом режиме, что может вызвать такие проблемы, как невозможность доступа к ЦП . Чтобы избежать этого, следует заблокировать инициализацию модуля, как описано ниже. Это также гарантирует, что запросы не будут обслуживаться до завершения инициализации. - с другой стороны, если инициализация синхронная, длительное время инициализации приведет к более длительным холодным запускам, что может стать проблемой, особенно для функций с низким параллелизмом во время пиков нагрузки.
Пример предварительного прогрева асинхронной библиотеки node.js
Node.js с Firestore — пример асинхронной библиотеки node.js. Чтобы воспользоваться min_instances, следующий код завершает загрузку и инициализацию во время загрузки, блокируя загрузку модуля.
Используется TLA, что означает необходимость ES6, использования расширения .mjs
для кода node.js или добавления type: module
в файл package.json.
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
Примеры глобальной инициализации
Node.js
const functions = require('firebase-functions'); let myCostlyVariable; exports.function = functions.https.onRequest((req, res) => { doUsualWork(); if(unlikelyCondition()){ myCostlyVariable = myCostlyVariable || buildCostlyVariable(); } res.status(200).send('OK'); });
Питон
from firebase_functions import https_fn # Always initialized (at cold-start) non_lazy_global = file_wide_computation() # Declared at cold-start, but only initialized if/when the function executes lazy_global = None @https_fn.on_request() def lazy_globals(request): global lazy_global, non_lazy_global # This value is initialized only if (and when) the function is called if not lazy_global: lazy_global = function_specific_computation() return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
Эта HTTP-функция использует лениво инициализированные глобальные переменные. Она принимает объект запроса ( flask.Request
) и возвращает текст ответа или любой набор значений, которые можно превратить в объект Response
с помощью make_response
.
Это особенно важно, если вы определяете несколько функций в одном файле, и разные функции используют разные переменные. Если вы не используете ленивую инициализацию, вы можете тратить ресурсы на переменные, которые инициализируются, но никогда не используются.
Дополнительные ресурсы
Узнайте больше об оптимизации производительности в видеоролике «Google Cloud Performance Atlas» Cloud Functions Cold Boot Time .