يصف هذا المستند أفضل الممارسات لتصميم Cloud Functions وتنفيذها واختبارها، ونشر Cloud Functions.
الدقة
يصف هذا القسم أفضل الممارسات العامة لتصميم وتنفيذ Cloud Functions.
كتابة دوال عديمة التأثير
يجب أن تعرض الدوال النتيجة نفسها حتى إذا تم استدعاؤها عدة مرات. يتيح لك ذلك إعادة محاولة استدعاء إذا تعذّر الاستدعاء السابق في منتصف الرمز البرمجي. لمزيد من المعلومات، يُرجى الاطّلاع على إعادة محاولة الدوال المستندة إلى الأحداث.
عدم بدء الأنشطة في الخلفية
النشاط في الخلفية هو أي نشاط يحدث بعد انتهاء دالتك.
ينتهي استدعاء الدالة عند عرض الدالة أو الإشارة إلى اكتمالها بطريقة أخرى، مثل استدعاء وسيطة callback في الدوال المستندة إلى الأحداث في Node.js. لا يمكن لأي رمز يتم تشغيله بعد الإنهاء السلس الوصول إلى وحدة المعالجة المركزية ولن يحقق أي تقدّم.
بالإضافة إلى ذلك، عند تنفيذ استدعاء لاحق في البيئة نفسها، يستأنف النشاط في الخلفية، ما يؤثر في الاستدعاء الجديد. قد يؤدي ذلك إلى سلوك غير متوقّع وأخطاء يصعُب تشخيصها. يؤدي الوصول إلى الشبكة بعد انتهاء الدالة عادةً إلى إعادة ضبط الاتصالات (رمز الخطأ ECONNRESET).
يمكن غالبًا رصد النشاط في الخلفية في السجلات من عمليات الاستدعاء الفردية، من خلال العثور على أي محتوى يتم تسجيله بعد السطر الذي يشير إلى انتهاء الاستدعاء. قد يكون النشاط في الخلفية مخفيًا أحيانًا في الرمز البرمجي، خاصةً عند توفّر عمليات غير متزامنة مثل عمليات معاودة الاتصال أو المؤقتات. راجِع الرمز البرمجي للتأكّد من انتهاء جميع العمليات غير المتزامنة قبل إنهاء الدالة.
حذف الملفات المؤقتة دائمًا
مساحة التخزين على القرص المحلي في الدليل المؤقت هي نظام ملفات في الذاكرة. تستهلك الملفات التي تكتبها الذاكرة المتاحة لدالتك، وتظل أحيانًا بين عمليات الاستدعاء. قد يؤدي عدم حذف هذه الملفات بشكلٍ صريح في النهاية إلى حدوث خطأ في الذاكرة وعملية تشغيل لاحقة على البارد.
يمكنك الاطّلاع على الذاكرة التي تستخدمها دالة فردية من خلال اختيارها في الـ قائمة الدوال في الـ Google Cloud Console واختيار الرسم البياني استخدام الذاكرة.
إذا كنت بحاجة إلى الوصول إلى مساحة تخزين طويلة الأجل، ننصحك باستخدام Cloud Run عمليات ربط وحدات التخزين مع Cloud Storage أو وحدات تخزين NFS.
يمكنك تقليل متطلبات الذاكرة عند معالجة الملفات الأكبر حجمًا باستخدام خطوط الأنابيب. على سبيل المثال، يمكنك معالجة ملف على Cloud Storage من خلال إنشاء مصدر قراءة، وتمريره من خلال عملية مستندة إلى المصدر، وكتابة مصدر الإخراج مباشرةً إلى Cloud Storage.
Functions Framework
لضمان تثبيت التبعيات نفسها بشكلٍ متّسق على مستوى البيئات، ننصحك بتضمين مكتبة Functions Framework في أداة إدارة الحِزم وتثبيت الاعتمادية على إصدار معيّن من Functions Framework.
لإجراء ذلك، ضِّمن الإصدار المفضّل في ملف القفل ذي الصلة (على سبيل المثال، package-lock.json لـ Node.js أو requirements.txt لـ Python).
إذا لم يتم إدراج Functions Framework بشكلٍ صريح كإحدى التبعيات، ستتم إضافته تلقائيًا أثناء عملية التصميم باستخدام أحدث إصدار متاح.
الأدوات
يقدّم هذا القسم إرشادات حول كيفية استخدام الأدوات لتنفيذ واختبار و التفاعل مع Cloud Functions.
التطوير على الجهاز
يستغرق نشر الدوال بعض الوقت، لذا غالبًا ما يكون اختبار رمز الدالة على الجهاز أسرع.
يمكن لمطوّري Firebase استخدام الـ Firebase CLI Cloud Functions Emulator.تجنُّب مهلات النشر أثناء التهيئة
إذا تعذّر نشر الدالة بسبب حدوث مهلة، من المحتمل أن يكون رمز النطاق العام للدالة يستغرق وقتًا طويلاً جدًا للتنفيذ أثناء عملية النشر.
تتضمّن Firebase CLI مهلة تلقائية لاكتشاف الدوال أثناء النشر. إذا تجاوزت منطق التهيئة في رمز مصدر الدوال (تحميل الوحدات النمطية وإجراء طلبات الشبكة وما إلى ذلك) هذه المهلة، قد يتعذّر النشر.
لتجنُّب المهلة، استخدِم إحدى الاستراتيجيات التالية:
(ننصح به) استخدام onInit() لتأجيل التهيئة
استخدِم الخطاف onInit() لتجنُّب تشغيل رمز التهيئة أثناء النشر. لن يتم تشغيل الرمز البرمجي داخل الخطاف onInit() إلا عند نشر الدالة في Cloud Run functions، وليس أثناء عملية النشر نفسها.
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}`); });
Python
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:
$ 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}`); });
Python
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.
من المهم بشكلٍ خاص تخزين اتصالات الشبكة ومراجع المكتبة وكائنات عميل واجهة برمجة التطبيقات مؤقتًا في النطاق العام. يمكنك الاطّلاع على تحسين الشبكات للحصول على أمثلة.
تقليل عمليات التشغيل على البارد من خلال ضبط الحد الأدنى لعدد المثيلات
تزيد Cloud Functions عدد المثيلات أو تقلّله تلقائيًا استنادًا إلى عدد الطلبات الواردة. يمكنك تغيير هذا السلوك التلقائي من خلال ضبط الحد الأدنى لعدد المثيلات التي يجب أن تحافظ Cloud Functions على جاهزيتها لتلبية الطلبات. يؤدي ضبط الحد الأدنى لعدد المثيلات إلى تقليل عمليات التشغيل على البارد لتطبيقك. ننصحك بضبط الحد الأدنى لعدد المثيلات وإكمال التهيئة في مدّة التحميل إذا كان تطبيقك حساسًا لوقت الاستجابة.
يمكنك الاطّلاع على التحكّم في سلوك التوسيع لمعرفة المزيد من المعلومات عن خيارات وقت التشغيل هذه.ملاحظات عن التشغيل على البارد والتهيئة
تحدث التهيئة العامة في مدّة التحميل. بدون ذلك، سيحتاج الطلب الأول إلى إكمال التهيئة وتحميل الوحدات النمطية، ما يؤدي إلى زيادة وقت الاستجابة.
ومع ذلك، تؤثر التهيئة العامة أيضًا في عمليات التشغيل على البارد. للحدّ من هذا التأثير، ننصحك بتهيئة ما هو مطلوب فقط للطلب الأول، للحفاظ على وقت استجابة الطلب الأول منخفضًا قدر الإمكان.
يُعدّ ذلك مهمًا بشكلٍ خاص إذا ضبطت الحد الأدنى لعدد المثيلات كما هو موضّح أعلاه لدالة حساسة لوقت الاستجابة. في هذا السيناريو، يضمن إكمال التهيئة في مدّة التحميل وتخزين البيانات المفيدة مؤقتًا عدم حاجة الطلب الأول إلى إجراء ذلك، ويتم الرد عليه بوقت استجابة منخفض.
إذا قمت بتهيئة المتغيّرات في النطاق العام، قد تؤدي أوقات التهيئة الطويلة إلى سلوكَين مختلفَين، وذلك حسب اللغة: - بالنسبة إلى بعض مجموعات اللغات والمكتبات غير المتزامنة، يمكن لإطار عمل الدالة التشغيل بشكلٍ غير متزامن وعرض النتيجة على الفور، ما يؤدي إلى استمرار تشغيل الرمز البرمجي في الخلفية، وقد يؤدي ذلك إلى مشاكل مثل عدم إمكانية الوصول إلى وحدة المعالجة المركزية. لتجنُّب ذلك، يجب حظر تهيئة الوحدة النمطية كما هو موضّح أدناه. يضمن ذلك أيضًا عدم الرد على الطلبات إلى أن تكتمل عملية التهيئة. - من ناحية أخرى، إذا كانت التهيئة متزامنة، سيؤدي وقت التهيئة الطويل إلى عمليات تشغيل أطول على البارد، ما قد يمثل مشكلة خاصةً مع الدوال ذات التزامن المنخفض أثناء الارتفاعات المفاجئة في التحميل.
مثال على التسخين المسبق لمكتبة غير متزامنة في 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'); });
Python
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 وقت التشغيل على البارد.