این سند بهترین شیوهها برای طراحی، پیادهسازی، آزمایش و استقرار Cloud Functions را شرح میدهد.
درستی
این بخش، بهترین شیوههای کلی برای طراحی و پیادهسازی Cloud Functions را شرح میدهد.
توابع خودتوان را بنویسید
Your functions should produce the same result even if they are called multiple times. This lets you retry an invocation if the previous invocation fails part way through your code. For more information, see retrying event-driven functions .
فعالیتهای پسزمینه را شروع نکنید
فعالیت پسزمینه هر چیزی است که پس از خاتمه تابع شما اتفاق میافتد. فراخوانی تابع زمانی پایان مییابد که تابع بازگشت دهد یا به هر طریق دیگری تکمیل را اعلام کند، مانند فراخوانی آرگومان callback در توابع رویدادگرای Node.js. هر کدی که پس از خاتمه تدریجی اجرا شود، نمیتواند به CPU دسترسی داشته باشد و هیچ پیشرفتی نخواهد داشت.
علاوه بر این، هنگامی که یک فراخوانی بعدی در همان محیط اجرا میشود، فعالیت پسزمینه شما از سر گرفته میشود و با فراخوانی جدید تداخل ایجاد میکند. این ممکن است منجر به رفتار غیرمنتظره و خطاهایی شود که تشخیص آنها دشوار است. دسترسی به شبکه پس از خاتمه یک تابع معمولاً منجر به تنظیم مجدد اتصالات میشود (کد خطای ECONNRESET ).
فعالیت پسزمینه اغلب میتواند در لاگهای مربوط به فراخوانیهای منفرد، با یافتن هر چیزی که پس از خطی که میگوید فراخوانی پایان یافته است، ثبت میشود، شناسایی شود. فعالیت پسزمینه گاهی اوقات میتواند در عمق کد پنهان شده باشد، به خصوص هنگامی که عملیات ناهمزمان مانند فراخوانیهای برگشتی یا تایمرها وجود دارند. کد خود را مرور کنید تا مطمئن شوید که تمام عملیات ناهمزمان قبل از خاتمه تابع، پایان یافتهاند.
همیشه فایلهای موقت را حذف کنید
ذخیرهسازی دیسک محلی در دایرکتوری موقت، یک سیستم فایل درون حافظهای است. فایلهایی که مینویسید، حافظه موجود برای تابع شما را مصرف میکنند و گاهی اوقات بین فراخوانیها باقی میمانند. عدم حذف صریح این فایلها در نهایت ممکن است منجر به خطای کمبود حافظه و شروع سرد بعدی شود.
شما میتوانید با انتخاب یک تابع در لیست توابع موجود در کنسول Google Cloud و انتخاب نمودار میزان استفاده از حافظه ، میزان حافظه استفاده شده توسط آن را مشاهده کنید.
اگر به دسترسی به فضای ذخیرهسازی بلندمدت نیاز دارید، استفاده از نگهدارندههای فضای ذخیرهسازی Cloud Run با Cloud Storage یا فضای ذخیرهسازی NFS را در نظر بگیرید.
شما میتوانید هنگام پردازش فایلهای بزرگتر با استفاده از pipelining، نیازهای حافظه را کاهش دهید. به عنوان مثال، میتوانید با ایجاد یک جریان خواندن، عبور دادن آن از یک فرآیند مبتنی بر جریان و نوشتن مستقیم جریان خروجی در Cloud Storage، یک فایل را در Cloud Storage پردازش کنید.
چارچوب توابع
برای اطمینان از اینکه وابستگیهای یکسان به طور مداوم در محیطهای مختلف نصب میشوند، توصیه میکنیم کتابخانه Functions Framework را در مدیریت بسته خود قرار دهید و وابستگی را به نسخه خاصی از Functions Framework پین کنید.
برای انجام این کار، نسخه مورد نظر خود را در فایل قفل مربوطه (برای مثال، package-lock.json برای Node.js یا requirements.txt برای پایتون) قرار دهید.
اگر Functions Framework به صراحت به عنوان یک وابستگی ذکر نشده باشد، به طور خودکار در طول فرآیند ساخت با استفاده از آخرین نسخه موجود اضافه خواهد شد.
ابزارها
این بخش دستورالعملهایی در مورد نحوه استفاده از ابزارها برای پیادهسازی، آزمایش و تعامل با Cloud Functions ارائه میدهد.
توسعه محلی
استقرار تابع کمی زمان میبرد، بنابراین اغلب تست کد تابع به صورت محلی سریعتر است.
توسعهدهندگان فایربیس میتوانند از شبیهساز Cloud Functions فایربیس CLI استفاده کنند.جلوگیری از وقفههای استقرار در طول راهاندازی اولیه
اگر استقرار تابع شما با خطای timeout با شکست مواجه شود، احتمالاً به این معنی است که کد دامنه سراسری تابع شما در طول فرآیند استقرار، زمان زیادی برای اجرا نیاز دارد.
رابط خط فرمان Firebase CLI) یک زمانبندی پیشفرض برای کشف توابع شما در حین استقرار دارد. اگر منطق مقداردهی اولیه در کد منبع توابع شما (بارگذاری ماژولها، برقراری تماسهای شبکه و غیره) از این زمانبندی تجاوز کند، ممکن است استقرار با شکست مواجه شود.
برای جلوگیری از وقفه زمانی، از یکی از استراتژیهای زیر استفاده کنید:
(توصیه میشود) onInit() برای به تعویق انداختن مقداردهی اولیه استفاده کنید
برای جلوگیری از اجرای کد مقداردهی اولیه در حین استقرار، از قلاب onInit() استفاده کنید. کد داخل قلاب onInit() فقط زمانی اجرا میشود که تابع در توابع Cloud Run مستقر شده باشد، نه در طول فرآیند استقرار.
نود جی اس
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() بازسازی کنید، میتوانید با استفاده از متغیر محیطی FUNCTIONS_DISCOVERY_TIMEOUT زمان استقرار CLI را افزایش دهید:
$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions
استفاده از Sendgrid برای ارسال ایمیل
Cloud Functions اجازه اتصال خروجی روی پورت ۲۵ را نمیدهند، بنابراین نمیتوانید اتصالهای غیر امن به سرور SMTP برقرار کنید. روش پیشنهادی برای ارسال ایمیل، استفاده از یک سرویس شخص ثالث مانند SendGrid است. میتوانید گزینههای دیگر برای ارسال ایمیل را در آموزش ارسال ایمیل از یک نمونه برای Google Compute Engine پیدا کنید.
عملکرد
این بخش بهترین شیوهها برای بهینهسازی عملکرد را شرح میدهد.
از همزمانی کم اجتناب کنید
از آنجا که شروع سرد (cold start) پرهزینه است، امکان استفاده مجدد از نمونههای اخیراً شروع شده در طول یک دوره اوج (spike) یک بهینهسازی عالی برای مدیریت بار است. محدود کردن همزمانی، نحوه استفاده از نمونههای موجود را محدود میکند، بنابراین باعث شروع سرد بیشتر میشود.
افزایش همزمانی به تعویق انداختن چندین درخواست در هر نمونه کمک میکند و مدیریت افزایش ناگهانی بار را آسانتر میکند.از وابستگیها عاقلانه استفاده کنید
Because functions are stateless, the execution environment is often initialized from scratch (during what is known as a cold start ). When a cold start occurs, the global context of the function is evaluated.
اگر توابع شما ماژولها را وارد کنند، زمان بارگذاری آن ماژولها میتواند به تأخیر فراخوانی در طول شروع سرد اضافه کند. شما میتوانید این تأخیر و همچنین زمان لازم برای استقرار تابع خود را با بارگذاری صحیح وابستگیها و عدم بارگذاری وابستگیهایی که تابع شما از آنها استفاده نمیکند، کاهش دهید.
استفاده از متغیرهای سراسری برای استفاده مجدد از اشیاء در فراخوانیهای آینده
There is no guarantee that the state of a function will be preserved for future invocations. However, Cloud Functions often recycles the execution environment of a previous invocation. If you declare a variable in global scope, its value can be reused in subsequent invocations without having to be recomputed.
به این ترتیب میتوانید اشیایی را که ممکن است ایجاد مجدد آنها در هر فراخوانی تابع پرهزینه باشد، ذخیره کنید. انتقال چنین اشیایی از بدنه تابع به محدوده سراسری میتواند منجر به بهبود قابل توجه عملکرد شود. مثال زیر یک شیء سنگین را فقط یک بار در هر نمونه تابع ایجاد میکند و آن را در تمام فراخوانیهای تابع که به نمونه داده شده میرسند، به اشتراک میگذارد:
نود جی اس
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 ) را میگیرد و متن پاسخ یا هر مجموعهای از مقادیر را که میتوانند با استفاده make_response به یک شیء Response تبدیل شوند، برمیگرداند.
به ویژه مهم است که اتصالات شبکه، ارجاعات کتابخانه و اشیاء کلاینت API را در محدوده سراسری ذخیره (cache) کنید. برای مثالها به بخش بهینهسازی شبکه مراجعه کنید.
با تنظیم حداقل تعداد دفعات روشن شدن موتور در حالت سرد، استارت سرد را کاهش دهید
به طور پیشفرض، Cloud Functions تعداد نمونهها را بر اساس تعداد درخواستهای ورودی مقیاسبندی میکند. شما میتوانید این رفتار پیشفرض را با تنظیم حداقل تعداد نمونههایی که Cloud Functions باید برای ارائه درخواستها آماده نگه دارد، تغییر دهید. تنظیم حداقل تعداد نمونهها، شروع سرد برنامه شما را کاهش میدهد. اگر برنامه شما به تأخیر حساس است، توصیه میکنیم حداقل تعداد نمونهها را تنظیم کنید و مقداردهی اولیه را در زمان بارگذاری تکمیل کنید.
برای اطلاعات بیشتر در مورد این گزینههای زمان اجرا، به کنترل رفتار مقیاسبندی مراجعه کنید.نکاتی در مورد شروع سرد و مقداردهی اولیه
مقداردهی اولیه سراسری در زمان بارگذاری اتفاق میافتد. بدون آن، اولین درخواست نیاز به تکمیل مقداردهی اولیه و بارگذاری ماژولها دارد، در نتیجه تأخیر بیشتری را متحمل میشود.
با این حال، مقداردهی اولیه سراسری نیز بر شروع سرد تأثیر میگذارد. برای به حداقل رساندن این تأثیر، فقط آنچه را که برای اولین درخواست لازم است مقداردهی اولیه کنید تا تأخیر اولین درخواست تا حد امکان کم باشد.
این امر به ویژه در صورتی اهمیت دارد که شما min instances را همانطور که در بالا توضیح داده شد برای یک تابع حساس به تأخیر پیکربندی کرده باشید. در این سناریو، تکمیل مقداردهی اولیه در زمان بارگذاری و ذخیره دادههای مفید تضمین میکند که اولین درخواست نیازی به انجام این کار ندارد و با تأخیر کم ارائه میشود.
If you initialize variables in global scope, depending on the language, long initialization times can result in two behaviors: - for some combination of languages and async libraries, the function framework can run asynchronously and return immediately, causing code to continue running in the background, which could cause issues such as not being able to access the CPU . To avoid this, you should block on module initialization as described below. This also ensures that requests are not served until the initialization is complete. - on the other hand, if the initialization is synchronous, the long initialization time will cause longer cold starts, which could be an issue especially with low concurrency functions during spikes of load.
مثالی از پیشگرم کردن یک کتابخانهی async node.js
Node.js با Firestore نمونهای از کتابخانه async 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" } }
نود جی اس
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. });
نمونههایی از مقداردهی اولیه سراسری
نود جی اس
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 ) را میگیرد و متن پاسخ یا هر مجموعهای از مقادیر را که میتوانند با استفاده از make_response به یک شیء Response تبدیل شوند، برمیگرداند.
این امر به ویژه در صورتی اهمیت دارد که چندین تابع را در یک فایل واحد تعریف کنید و توابع مختلف از متغیرهای مختلفی استفاده کنند. مگر اینکه از مقداردهی اولیهی تنبل استفاده کنید، ممکن است منابع را برای متغیرهایی که مقداردهی اولیه شدهاند اما هرگز استفاده نشدهاند، هدر دهید.
منابع اضافی
برای کسب اطلاعات بیشتر در مورد بهینهسازی عملکرد، ویدیوی «اطلس عملکرد گوگل کلود» Cloud Functions «زمان بوت سرد توابع ابری » را ببینید.