إدراج الدوال في قائمة انتظار مع "مهام Cloud"


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

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

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

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

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

إنشاء دوال قوائم انتظار المهام

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

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

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

في معظم الحالات، يُعدّ محاكي Cloud Functions أفضل طريقة لاختبار دوال قائمة انتظار المهام. اطّلِع على مستندات Emulator Suite للتعرّف على كيفية تجهيز تطبيقك لمحاكاة وظائف قائمة المهام.

بالإضافة إلى ذلك، يتم عرض functions_sdk في قائمة المهام كمهام بسيطة لدوالّ HTTP في Firebase Local Emulator Suite. يمكنك اختبار وظيفة مهمة محاكية من خلال إرسال طلب POST باستخدام HTTP مع حمولة بيانات 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 مع خيارات (تقييد معدّل تكرار المحاولة وإعادة المحاولة) محددة في رمز المصدر.

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

إضافة وظائف قائمة انتظار المهام إلى قائمة الانتظار

يمكن إضافة دوال قائمة انتظار المهام إلى Cloud Tasks من بيئة خادم موثوق بها مثل Cloud Functions for Firebase باستخدام Firebase Admin SDK لأجل Node.js أو مكتبات Google Cloud لبرنامج Python. إذا كنت مستخدِمًا جديدًا لخدمات Admin 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")
  • يحاول الرمز النموذجي توزيع تنفيذ tasks عن طريق ربط تأخير مدته N دقيقة بالمهمة N. ويؤدي ذلك إلى بدء مهمة واحدة تقريبًا في الدقيقة. يُرجى العلم أنّه يمكنك أيضًا استخدام scheduleTime (Node.js) أو schedule_time (Python) إذا كنت تريد أن يبدأ Cloud Tasks مهمة في وقت محدّد.

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

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

بسبب الطريقة التي تُنشئ بها خدمة Cloud Tasks الرموز المميّزة للمصادقة لمصادقة الطلبات المرسَلة إلى وظائف قائمة انتظار المهام الأساسية، عليك تحديد عنوان 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

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

Cloud Tasks">تفعيل تسجيل Cloud Tasks

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

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

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

  • يجب أن تحصل الهوية المستخدَمة لإضافة المهام إلى "قائمة الانتظار" في Cloud Tasks على إذن 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 Tasks تحتاج إلى إذن لاستخدام حساب الخدمة المرتبط بمهمة في Cloud Tasks.

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

اطّلِع على مستندات "إدارة الهوية وإمكانية الوصول" في Google Cloud للحصول على تعليمات حول كيفية إضافة حساب الخدمة التلقائي 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