نکات و ترفندها

این سند روال‌های مطلوب را برای طراحی، پیاده‌سازی، آزمایش، و استقرار Cloud Functions شرح می‌دهد.

درستی

این بخش روال‌های مطلوب کلی را برای طراحی و پیاده‌سازی Cloud Functions شرح می‌دهد.

نوشتن توابع خودتوان

توابع شما باید حتی اگر چندین بار فراخوانی شوند نتیجه یکسانی تولید کنند. این کار به شما امکان می‌دهد اگر فراخوانی قبلی در بخشی از کدتان ناموفق بود، فراخوانی را دوباره امتحان کنید. برای اطلاعات بیشتر، به تلاش مجدد برای توابع رویدادمحور مراجعه کنید.

فعالیت‌های پس‌زمینه شروع نشود

فعالیت پس‌زمینه هر چیزی است که پس‌از پایان یافتن تابع شما اتفاق می‌افتد. فراخوانی تابع زمانی پایان می‌یابد که تابع برگردانده شود یا به روشی دیگر تکمیل را نشان دهد، مثلاً با فراخوانی آرگومان callback در توابع رویدادمحور Node.js. هر کدی که پس‌از پایان خوش اجرا شود نمی‌تواند به CPU دسترسی داشته باشد و هیچ پیشرفتی نخواهد داشت.

علاوه‌براین، وقتی فراخوانی بعدی در همان محیط اجرا می‌شود، فعالیت پس‌زمینه‌ای شما ازسر گرفته می‌شود و با فراخوانی جدید تداخل پیدا می‌کند. این کار ممکن است منجر به رفتار غیرمنتظره و خطاهایی شود که تشخیص آن‌ها دشوار است. دسترسی به شبکه پس‌از پایان یافتن تابع معمولاً منجر به بازنشانی اتصالات می‌شود (کد خطای ECONNRESET).

فعالیت پس‌زمینه‌ای را اغلب می‌توان در گزارش‌های فراخوانی‌های فردی تشخیص داد، با یافتن هر چیزی که پس‌از خطی که می‌گوید فراخوانی به‌پایان رسیده است ثبت شده باشد. فعالیت پس‌زمینه‌ای گاهی اوقات می‌تواند در کد پنهان‌تر باشد، به‌ویژه زمانی که عملیات ناهمزمان مانند تماس‌های برگشتی یا زمان‌سنج‌ها وجود دارند. کدتان را مرور کنید تا مطمئن شوید همه عملیات ناهم‌زمان قبل‌از اینکه تابع را خاتمه دهید تمام می‌شوند.

همیشه فایل‌های موقت حذف شود

فضای ذخیره‌سازی دیسک محلی در فهرست راهنمای موقت یک سیستم فایل در حافظه است. فایل‌هایی که می‌نویسید از حافظه دردسترس تابع شما استفاده می‌کنند و گاهی اوقات بین فراخوانی‌ها ماندگار می‌شوند. حذف نکردن صریح این فایل‌ها ممکن است درنهایت منجر به خطای کمبود حافظه و راه‌اندازی مجدد شود.

با انتخاب تابع موردنظر در فهرست توابع در کنسول Google Cloud و انتخاب طرح مصرف حافظه، می‌توانید حافظه مصرفی هر تابع را ببینید.

اگر به فضای ذخیره‌سازی بلندمدت نیاز دارید، از Cloud Run اتصال‌های حجم با Cloud Storage یا حجم‌های NFS استفاده کنید.

بااستفاده از خط لوله می‌توانید الزامات حافظه را هنگام پردازش فایل‌های بزرگ‌تر کاهش دهید. برای مثال، می‌توانید با ایجاد یک جاری‌سازی خواندن، گذراندن آن از یک فرایند مبتنی بر جاری‌سازی، و نوشتن جاری‌سازی برونداد مستقیماً در Cloud Storage، فایلی را در Cloud Storage پردازش کنید.

چارچوب توابع

برای اطمینان از اینکه وابستگی‌های یکسانی در همه محیط‌ها نصب می‌شوند، توصیه می‌کنیم کتابخانه «چارچوب کارکردها» را در مدیر بسته خود بگنجانید و وابستگی را به نسخه خاصی از «چارچوب کارکردها» سنجاق کنید.

برای انجام این کار، نسخه ترجیحی‌تان را در فایل قفل مربوطه بگنجانید (برای نمونه، package-lock.json برای Node.js، یا requirements.txt برای Python).

اگر «چارچوب توابع» به‌طور صریح به‌عنوان وابستگی فهرست نشده باشد، درطول فرایند ساخت بااستفاده از جدیدترین نسخه دردسترس به‌طور خودکار اضافه خواهد شد.

ابزارها

این بخش رهنمودهایی درباره نحوه استفاده از ابزارها برای پیاده‌سازی، آزمایش، و تعامل با Cloud Functions ارائه می‌دهد.

توسعه محلی

استقرار تابع کمی زمان می‌برد، بنابراین اغلب سریع‌تر است که کد تابع خود را به‌صورت محلی آزمایش کنید.

توسعه‌دهندگان Firebase می‌توانند از Firebase CLI Cloud Functions Emulator استفاده کنند.

پرهیز از زمان‌های اتمام استقرار درطول مقداردهی اولیه

اگر استقرار تابع شما با خطای مهلت زمانی ناموفق باشد، احتمالاً به این معنی است که کد حوزه جهانی تابع شما درطول فرایند استقرار خیلی طول می‌کشد تا اجرا شود.

Firebase CLI برای شناسایی توابع شما درطول استقرار، زمان انتظار پیش‌فرضی دارد. اگر منطق مقداردهی اولیه در کد منبع توابع شما (بار کردن واحدها، برقراری تماس‌های شبکه، و غیره) از این زمان اتمام تجاوز کند، ممکن است استقرار ناموفق باشد.

برای جلوگیری از اتمام زمان، از یکی از استراتژی‌های زیر استفاده کنید:

از قلاب 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() بازسازی کنید، می‌توانید بااستفاده از متغیر محیط FUNCTIONS_DISCOVERY_TIMEOUT، زمان اتمام استقرار CLI را افزایش دهید:

$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions

استفاده از Sendgrid برای ارسال ایمیل

‫Cloud Functions اجازه اتصال خروجی در درگاه ۲۵ را نمی‌دهد، بنابراین نمی‌توانید اتصال غیرامنی به سرور 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) را می‌گیرد و نوشتار پاسخ یا هر مجموعه مقداری را که می‌تواند بااستفاده از make_response به شیء Response تبدیل شود برمی‌گرداند.

به‌ویژه مهم است که اتصالات شبکه، ارجاعات کتابخانه، و اشیای مشتری API را در حوزه جهانی ذخیره کنید. برای مثال‌ها، بهینه‌سازی شبکه را ببینید.

با تنظیم حداقل تعداد نمونه، شروع سرد را کاهش دهید

به‌طور پیش‌فرض، «توابع ابری» تعداد نمونه‌ها را براساس تعداد درخواست‌های ورودی مقیاس‌بندی می‌کند. با تنظیم حداقل تعداد نمونه‌هایی که Cloud Functions باید برای ارائه درخواست‌ها آماده نگه دارد، می‌توانید این رفتار پیش‌فرض را تغییر دهید. تنظیم حداقل تعداد نمونه‌ها باعث کاهش شروع‌های سرد برنامه شما می‌شود. اگر برنامه شما به تأخیر حساس است، توصیه می‌کنیم حداقل تعداد نمونه‌ها را تنظیم کنید و مقداردهی اولیه را در زمان بار کردن تکمیل کنید.

برای اطلاعات بیشتر درباره این گزینه‌های زمان اجرا، کنترل رفتار مقیاس‌بندی را ببینید.

نکته‌هایی درباره شروع سرد و مقداردهی اولیه

مقداردهی اولیه سراسری در زمان بار کردن انجام می‌شود. بدون آن، درخواست اول باید راه‌اندازی را تکمیل کند و واحدها را بار کند، و درنتیجه تأخیر بیشتری ایجاد می‌شود.

بااین‌حال، مقداردهی اولیه سراسری نیز بر شروع سرد تأثیر می‌گذارد. برای به‌حداقل رساندن این تأثیر، فقط آنچه را برای درخواست اول لازم است مقداردهی اولیه کنید تا تأخیر درخواست اول تا حد امکان کم باشد.

این موضوع مخصوصاً اگر نمونه‌های کمینه را همان‌طور که در بالا برای تابع حساس به تأخیر توضیح داده شد پیکربندی کرده باشید اهمیت خاصی دارد. در آن سناریو، تکمیل مقداردهی اولیه در زمان بارگیری و ذخیره کردن داده‌های مفید در حافظه نهان تضمین می‌کند که اولین درخواست نیازی به انجام این کار ندارد و با تأخیر کم ارائه می‌شود.

اگر متغیرها را در محدوده سراسری مقداردهی اولیه کنید، بسته به زبان، زمان‌های مقداردهی اولیه طولانی می‌تواند منجر به دو رفتار شود: - برای برخی‌از ترکیب‌های زبان و کتابخانه‌های ناهم‌زمان، چارچوب تابع می‌تواند به‌صورت ناهم‌زمان اجرا شود و بلافاصله برگردد، که باعث می‌شود کد در پس‌زمینه به اجرا ادامه دهد و می‌تواند مشکلاتی مانند عدم امکان دسترسی به CPU ایجاد کند. برای جلوگیری از این اتفاق، باید طبق شرح زیر، در مقداردهی اولیه واحد مسدود کنید. این کار همچنین تضمین می‌کند که تا زمانی که مقداردهی اولیه کامل نشده است، درخواست‌ها ارائه نشوند. - از طرف دیگر، اگر مقداردهی اولیه هم‌زمان باشد، زمان مقداردهی اولیه طولانی باعث شروع سرد طولانی‌تر می‌شود که می‌تواند به‌ویژه در توابع با هم‌زمانی پایین درطول اوج بار مشکل‌ساز شود.

نمونه‌ای از پیش‌گرم کردن کتابخانه ناهم‌زمان 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) را می‌گیرد و نوشتار پاسخ یا هر مجموعه مقداری را که بااستفاده از make_response بتواند به شیء Response تبدیل شود برمی‌گرداند.

این موضوع به‌ویژه زمانی اهمیت دارد که چندین تابع را در یک فایل تعریف کنید و توابع مختلف از متغیرهای مختلف استفاده کنند. اگر از مقداردهی اولیه تنبل استفاده نکنید، ممکن است منابع را برای متغیرهایی که مقداردهی اولیه شده‌اند اما هرگز استفاده نشده‌اند هدر دهید.

منابع بیشتر

درباره بهینه‌سازی عملکرد در ویدیو «اطلس عملکرد Google Cloud» Cloud Functions زمان راه‌اندازی سرد بیشتر بدانید.