إدراج الدوال في قائمة الانتظار باستخدام "مهام السحابة الإلكترونية"


تستفيد وظائف قائمة انتظار المهام من مهام Google Cloud لمساعدة تطبيقك في تنفيذ مهام تستهلك الوقت أو تستهلك موارد موارد كامل أو تكون محدودة في معدّل نقل البيانات بشكل غير متزامن خارج مسار التطبيق الرئيسي.

على سبيل المثال، تخيل أنك تريد إنشاء نسخ احتياطية من مجموعة كبيرة من ملفات الصور المستضافة حاليًا على واجهة برمجة تطبيقات مع حد لمعدل الزيارات. لكي تصبح مستهلكًا مسؤولاً لواجهة برمجة التطبيقات هذه، عليك الالتزام بحدود السعر الخاصة به. بالإضافة إلى ذلك، قد يكون هذا النوع من الوظائف طويلة الأمد عرضةً للفشل بسبب المهلات وحدود الذاكرة.

للتخفيف من التعقيد هذا، يمكنك كتابة دالة قائمة انتظار للمهام التي تحدد خيارات المهام الأساسية مثل scheduleTime وdispatchDeadline، ثم تسليم الوظيفة إلى قائمة انتظار في "مهام السحابة الإلكترونية". تم تصميم بيئة Cloud Tasks خصيصًا لضمان توفير سياسات فعالة للتحكم في تكدس البيانات وإعادة المحاولة لهذه الأنواع من العمليات.

يمكن تشغيل الإصدار 3.20.1 من حزمة تطوير البرامج (SDK) لمنصة Firebase for Cloud Functions for Firebase والإصدارات الأحدث، مع الإصدار 10.2.0 والإصدارات الأحدث من حزمة تطوير البرامج (SDK) لمشرفي Firebase، والإصدارات الأحدث لدعم وظائف قائمة انتظار المهام.

ويمكن أن يؤدي استخدام وظائف قائمة المهام مع Firebase إلى تحصيل رسوم مقابل معالجة "مهام Cloud". يُرجى الاطّلاع على أسعار Cloud Tasks للحصول على مزيد من المعلومات.

إنشاء دوال قائمة المهام

لاستخدام وظائف قائمة انتظار المهام، اتبع سير العمل التالي:

  1. اكتب دالة قائمة انتظار المهام باستخدام حزمة تطوير البرامج (SDK) لمنصّة Firebase لوظائف السحابة الإلكترونية.
  2. اختبِر الدالة من خلال تشغيلها باستخدام طلب HTTP.
  3. انشُر الدالة باستخدام واجهة سطر الأوامر في Firebase. عند نشر وظيفة قائمة انتظار المهام لأول مرة، سينشئ واجهة سطر الأوامر قائمة انتظار مهام في مهام السحابة الإلكترونية مع تحديد خيارات (تحديد المعدل وإعادة المحاولة) في رمز المصدر.
  4. إضافة مهام إلى قائمة انتظار المهام التي تم إنشاؤها حديثًا، وتمرير المعلمات لإعداد جدول تنفيذي إذا لزم الأمر. يمكنك تحقيق ذلك من خلال كتابة الرمز باستخدام "SDK للمشرف" ونشره في Cloud Functions for Firebase.

كتابة دوال قائمة المهام

وتستند عيّنات التعليمات البرمجية في هذا القسم إلى تطبيق يُعدّ خدمة للاحتفاظ بنسخة احتياطية من جميع الصور المأخوذة من صورة اليوم الفلكي التابعة لوكالة ناسا. للبدء، عليك استيراد الوحدات المطلوبة:

Node.js

// Dependencies for task queue functions.
const {onTaskDispatched} = require("firebase-functions/v2/tasks");
const {onRequest, HttpsError} = require("firebase-functions/v2/https");
const {getFunctions} = require("firebase-admin/functions");
const {logger} = require("firebase-functions/v2");

// Dependencies for image backup.
const path = require("path");
const fetch = require("node-fetch");
const {initializeApp} = require("firebase-admin/app");
const {getStorage} = require("firebase-admin/storage");
const {GoogleAuth} = require("google-auth-library");

Python

# Dependencies for task queue functions.
from google.cloud import tasks_v2
import requests
from firebase_functions.options import RetryConfig, RateLimits, SupportedRegion

# Dependencies for image backup.
from datetime import datetime, timedelta
import json
import pathlib
from urllib.parse import urlparse
from firebase_admin import initialize_app, storage, functions
from firebase_functions import https_fn, tasks_fn, params
import google.auth
from google.auth.transport.requests import AuthorizedSession

يمكنك استخدام onTaskDispatched أو on_task_dispatched لوظائف قائمة المهام. عند كتابة دالة قائمة انتظار مهام، يمكنك تعيين إعادة المحاولة لكل قائمة انتظار والتهيئة المحددة للمعدل.

ضبط وظائف قائمة انتظار المهام

تأتي دوال قائمة المهام مع مجموعة قوية من إعدادات التهيئة للتحكم بدقة في حدود المعدل وإعادة محاولة سلوك قائمة انتظار المهام:

Node.js

exports.backupapod = onTaskDispatched(
    {
      retryConfig: {
        maxAttempts: 5,
        minBackoffSeconds: 60,
      },
      rateLimits: {
        maxConcurrentDispatches: 6,
      },
    }, async (req) => {

Python

@tasks_fn.on_task_dispatched(retry_config=RetryConfig(max_attempts=5, min_backoff_seconds=60),
                             rate_limits=RateLimits(max_concurrent_dispatches=10))
def backupapod(req: tasks_fn.CallableRequest) -> str:
    """Grabs Astronomy Photo of the Day (APOD) using NASA's API."""
  • retryConfig.maxAttempts=5: تتم إعادة محاولة كل مهمة في قائمة المهام تلقائيًا حتى 5 مرات. ويساعد ذلك في الحدّ من الأخطاء المؤقتة مثل أخطاء الشبكة أو انقطاع الخدمة المؤقت لخدمة خارجية تابعة.

  • retryConfig.minBackoffSeconds=60: تتم إعادة محاولة كل مهمة لمدة 60 ثانية على الأقل بغض النظر عن كل محاولة. يوفر هذا موردًا احتياطيًا كبيرًا بين كل محاولة حتى لا نتسرع في استنفاد المحاولات الخمس لإعادة المحاولة بسرعة كبيرة.

  • rateLimits.maxConcurrentDispatch=6: يتم إرسال 6 مهام كحد أقصى في وقت محدد. يساعد ذلك في ضمان تدفق ثابت للطلبات إلى الدالة الأساسية، كما يساعد في تقليل عدد الحالات النشطة وعمليات التشغيل على البارد.

اختبار دوال قائمة المهام

يتم عرض دوال قائمة المهام في "مجموعة أدوات المحاكاة المحلية من Firebase" على هيئة دوال HTTP بسيطة. يمكنك اختبار دالة تمت محاكاتها عن طريق إرسال طلب HTTP POST مع حمولة بيانات JSON:

 # start the Local Emulator Suite
 firebase emulators:start

 # trigger the emulated task queue function
 curl \
  -X POST                                            # An HTTP POST request...
  -H "content-type: application/json" \              # ... with a JSON body
  http://localhost:$PORT/$PROJECT_ID/$REGION/$NAME \ # ... to function url
  -d '{"data": { ... some data .... }}'              # ... with JSON encoded data

نشر دوال قائمة المهام

نشر دالة قائمة انتظار المهام باستخدام واجهة سطر الأوامر لـ Firebase:

$ firebase deploy --only functions:backupapod

عند نشر وظيفة قائمة انتظار مهام لأول مرة، ينشئ واجهة سطر الأوامر قائمة انتظار مهام في Cloud Tasks بخيارات محددة في رمز المصدر (تحديد المعدل وإعادة المحاولة).

إذا واجهتك أخطاء في الأذونات عند نشر الدوال، تأكَّد من إسناد أدوار "إدارة الهوية وإمكانية الوصول" المناسبة إلى المستخدم الذي يدير أوامر النشر.

إدراج دوال قائمة انتظار المهام

يمكن إضافة وظائف قائمة انتظار المهام إلى "مهام Google" في "مهام Cloud" من بيئة خادم موثوق بها، مثل Cloud Functions for Firebase، وذلك باستخدام "حزمة SDK الخاصة بمشرف Firebase" من أجل Node.js أو مكتبات Google Cloud للغة Python. إذا كنت مستخدمًا جديدًا لـ "حزم SDK للمشرف"، يُرجى الاطّلاع على إضافة Firebase إلى خادم للبدء.

ينشئ المسار النموذجي مهمة جديدة ويضيفها إلى قائمة الانتظار في Cloud Tasks، ويضبط إعدادات المهمة:

Node.js

exports.enqueuebackuptasks = onRequest(
    async (_request, response) => {
      const queue = getFunctions().taskQueue("backupapod");
      const targetUri = await getFunctionUrl("backupapod");

      const enqueues = [];
      for (let i = 0; i <= BACKUP_COUNT; i += 1) {
        const iteration = Math.floor(i / HOURLY_BATCH_SIZE);
        // Delay each batch by N * hour
        const scheduleDelaySeconds = iteration * (60 * 60);

        const backupDate = new Date(BACKUP_START_DATE);
        backupDate.setDate(BACKUP_START_DATE.getDate() + i);
        // Extract just the date portion (YYYY-MM-DD) as string.
        const date = backupDate.toISOString().substring(0, 10);
        enqueues.push(
            queue.enqueue({date}, {
              scheduleDelaySeconds,
              dispatchDeadlineSeconds: 60 * 5, // 5 minutes
              uri: targetUri,
            }),
        );
      }
      await Promise.all(enqueues);
      response.sendStatus(200);
    });

Python

@https_fn.on_request()
def enqueuebackuptasks(_: https_fn.Request) -> https_fn.Response:
    """Adds backup tasks to a Cloud Tasks queue."""
    task_queue = functions.task_queue("backupapod")
    target_uri = get_function_url("backupapod")

    for i in range(BACKUP_COUNT):
        batch = i // HOURLY_BATCH_SIZE

        # Delay each batch by N hours
        schedule_delay = timedelta(hours=batch)
        schedule_time = datetime.now() + schedule_delay

        dispatch_deadline_seconds = 60 * 5  # 5 minutes

        backup_date = BACKUP_START_DATE + timedelta(days=i)
        body = {"data": {"date": backup_date.isoformat()[:10]}}
        task_options = functions.TaskOptions(schedule_time=schedule_time,
                                             dispatch_deadline_seconds=dispatch_deadline_seconds,
                                             uri=target_uri)
        task_queue.enqueue(body, task_options)
    return https_fn.Response(status=200, response=f"Enqueued {BACKUP_COUNT} tasks")
  • يحاول نموذج التعليمة البرمجية نشر تنفيذ المهام عن طريق ربط تأخير دقيق بـ Nth للمهمة Nth. ويترجم هذا إلى تشغيل حوالي مهمة واحدة في الدقيقة. يُرجى العِلم أنّه يمكنك أيضًا استخدام scheduleTime (Node.js) أو schedule_time (Python) إذا كنت تريد أن تشغِّل Cloud Tasks مهمة في وقت محدّد.

  • يحدد الرمز النموذجي الحد الأقصى للوقت الذي ستنتظره "مهام Cloud" حتى تكتمل المهمة. ستعيد خدمة Cloud Tasks المهمة بعد إعادة محاولة إعداد قائمة الانتظار أو حتى الوصول إلى هذا الموعد النهائي. في العينة، يتم ضبط قائمة الانتظار لإعادة محاولة تنفيذ المهمة حتى 5 مرات، ولكن يتم إلغاء المهمة تلقائيًا إذا استغرقت العملية بأكملها (بما في ذلك محاولات إعادة المحاولة) أكثر من 5 دقائق.

استرداد عنوان URI المستهدف وتضمينه

بسبب الطريقة التي تنشئ بها خدمة "مهام Cloud" رموز المصادقة المميّزة لمصادقة الطلبات إلى وظائف قائمة انتظار المهام الأساسية، يجب تحديد عنوان URL لتشغيل Cloud Run للدالة عند إدراج المهام في "قائمة المحتوى التالي". ننصحك باسترداد عنوان URL الخاص بوظيفتك آليًا كما هو موضّح أدناه:

Node.js

/**
 * Get the URL of a given v2 cloud function.
 *
 * @param {string} name the function's name
 * @param {string} location the function's location
 * @return {Promise<string>} The URL of the function
 */
async function getFunctionUrl(name, location="us-central1") {
  if (!auth) {
    auth = new GoogleAuth({
      scopes: "https://www.googleapis.com/auth/cloud-platform",
    });
  }
  const projectId = await auth.getProjectId();
  const url = "https://cloudfunctions.googleapis.com/v2beta/" +
    `projects/${projectId}/locations/${location}/functions/${name}`;

  const client = await auth.getClient();
  const res = await client.request({url});
  const uri = res.data?.serviceConfig?.uri;
  if (!uri) {
    throw new Error(`Unable to retreive uri for function at ${url}`);
  }
  return uri;
}

Python

def get_function_url(name: str, location: str = SupportedRegion.US_CENTRAL1) -> str:
    """Get the URL of a given v2 cloud function.

    Params:
        name: the function's name
        location: the function's location

    Returns: The URL of the function
    """
    credentials, project_id = google.auth.default(
        scopes=["https://www.googleapis.com/auth/cloud-platform"])
    authed_session = AuthorizedSession(credentials)
    url = ("https://cloudfunctions.googleapis.com/v2beta/" +
           f"projects/{project_id}/locations/{location}/functions/{name}")
    response = authed_session.get(url)
    data = response.json()
    function_url = data["serviceConfig"]["uri"]
    return function_url

تحديد المشاكل وحلّها

تفعيل تسجيل "مهام Google" في السحابة الإلكترونية

تحتوي السجلات من Cloud Tasks على معلومات تشخيصية مفيدة مثل حالة الطلب المرتبط بمهمة معيّنة. يتم تلقائيًا إيقاف السجلّات من "مهام Google" بسبب حجم السجلات الكبير التي من المحتمل أن تنشئها في مشروعك. ننصحك بتفعيل سجلات تصحيح الأخطاء أثناء تطوير وظائف قائمة المهام وتصحيحها. اطّلِع على تفعيل تسجيل الدخول.

أذونات "إدارة الهوية وإمكانية الوصول"

قد تظهر لك أخطاء PERMISSION DENIED عند إدراج المهام في "قائمة المحتوى التالي" أو عندما تحاول خدمة Cloud Tasks استدعاء وظائف قائمة المهام. تأكَّد من أنّ مشروعك يتضمّن عمليات ربط إدارة الهوية وإمكانية الوصول التالية:

  • يجب أن تحصل الهوية المستخدَمة لإدراج المهام في قائمة "مهام Cloud" على إذن "إدارة الهوية وإمكانية الوصول" (IAM) cloudtasks.tasks.create.

    في النموذج، هذا هو حساب الخدمة التلقائي في App Engine.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member=serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com \
  --role=roles/cloudtasks.enqueuer
  • تحتاج الهوية المستخدمة لإدراج المهام في قائمة مهام Cloud إلى إذن لاستخدام حساب الخدمة المرتبط بمهمة في Cloud Tasks.

    في النموذج، يكون هذا هو حساب الخدمة التلقائي في App Engine.

يمكنك الاطّلاع على وثائق Google Cloud IAM للحصول على تعليمات حول كيفية إضافة حساب خدمة App Engine التلقائي كمستخدم لحساب الخدمة التلقائي لـ App Engine.

gcloud functions add-iam-policy-binding $FUNCTION_NAME \
  --region=us-central1 \
  --member=serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com \
  --role=roles/cloudfunctions.invoker