يصف هذا المستند أفضل الممارسات لتصميم Cloud Functions وتنفيذه واختباره ونشره.
الدقة
يصف هذا القسم أفضل الممارسات العامة لتصميم Cloud 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 مهلة تلقائية لاكتشاف الدوال أثناء النشر. إذا تجاوز منطق الإعداد في الرمز المصدر للدوال (تحميل الوحدات، وإجراء طلبات الشبكة، وما إلى ذلك) هذه المهلة، قد يتعذّر النشر.
لتجنُّب انتهاء المهلة، استخدِم إحدى الاستراتيجيات التالية:
(إجراء يُنصح به) استخدِم 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}`); });
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 وقت التشغيل العادي.