توابع ناهمزمان را دوباره امتحان کنید

این سند توضیح می‌دهد که چگونه می‌توانید از توابع پس‌زمینه ناهمزمان (غیر HTTPS) درخواست کنید تا در صورت شکست، دوباره تلاش کنند.

چرا توابع رویداد محور تکمیل نمی‌شوند؟

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

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

  • تابع حاوی یک باگ است و زمان اجرا یک استثنا ایجاد می‌کند.
  • تابع نمی‌تواند به نقطه پایانی سرویس برسد، یا در حین تلاش برای انجام این کار، زمان آن تمام می‌شود.
  • تابع عمداً یک استثنا ایجاد می‌کند (برای مثال، وقتی یک پارامتر اعتبارسنجی نمی‌شود).
  • یک تابع Node.js یک promise رد شده را برمی‌گرداند، یا یک مقدار غیر null را به یک callback ارسال می‌کند.

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

معناشناسی تلاش مجدد

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

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

پیکربندی تلاش‌های مجدد از کد تابع شما

با استفاده Cloud Functions for Firebase ، می‌توانید امکان تلاش مجدد را در کد برای یک تابع فعال کنید. برای انجام این کار برای یک رویداد پس‌زمینه مانند ایجاد یک سند جدید فایراستور، گزینه failurePolicy (نسل اول) یا retry policy (نسل دوم) را روی true تنظیم کنید:

نسل اول

exports.docCreated = functions
  .runWith({
    // retry on failure
    failurePolicy: true,
  })
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    /* ... */
  });

نسل دوم

const { onDocumentCreated } = require("firebase-functions/firestore");

exports.docCreated = onDocumentCreated(
  {
    // retry on failure
    retry: true,
  },
  "my-collection/{docId}",
  (event) => {
    /* ... */
  },
);

تنظیم true همانطور که نشان داده شده است، تابع را طوری پیکربندی می‌کند که در صورت شکست، دوباره تلاش کند.

پنجره تلاش مجدد

برای توابع نسل دوم، این پنجره‌ی تلاش مجدد پس از ۲۴ ساعت منقضی می‌شود. برای توابع نسل اول، پس از ۷ روز منقضی می‌شود. Cloud Functions توابع رویدادمحور تازه ایجاد شده را با استفاده از یک استراتژی بازگشت نمایی، با افزایش بازگشت بین ۱۰ تا ۶۰۰ ثانیه، دوباره امتحان می‌کنند. این خط‌مشی برای توابع جدید در اولین باری که آنها را مستقر می‌کنید اعمال می‌شود. این خط‌مشی برای توابع موجودی که قبل از اعمال تغییرات شرح داده شده در این یادداشت انتشار، برای اولین بار مستقر شده‌اند، به صورت عطف به ماسبق اعمال نمی‌شود، حتی اگر توابع را دوباره مستقر کنید.

بهترین شیوه‌ها

این بخش بهترین شیوه‌ها برای استفاده از تلاش‌های مجدد را شرح می‌دهد.

استفاده از تلاش مجدد برای مدیریت خطاهای گذرا

از آنجا که تابع شما تا زمان اجرای موفقیت‌آمیز به طور مداوم تلاش مجدد (retry) می‌شود، خطاهای دائمی مانند باگ‌ها (bugs) باید قبل از فعال کردن تلاش مجدد، از طریق آزمایش از کد شما حذف شوند. تلاش مجدد بهترین راه برای مدیریت خطاهای متناوب یا گذرا است که احتمال زیادی برای حل آنها پس از تلاش مجدد وجود دارد، مانند یک نقطه پایانی سرویس ناپایدار یا timeout.

برای جلوگیری از حلقه‌های تکرار بی‌نهایت، یک شرط پایان تنظیم کنید

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

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

برای مثال، این قطعه کد تمام رویدادهای قدیمی‌تر از ۱۰ ثانیه را نادیده می‌گیرد:

const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
  console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
  callback();
  return;
}

استفاده از catch به همراه Promiseها

اگر تابع شما قابلیت تلاش مجدد را فعال کرده باشد، هر خطای مدیریت نشده‌ای باعث تلاش مجدد می‌شود. مطمئن شوید که کد شما هر خطایی را که نباید منجر به تلاش مجدد شود، ثبت می‌کند.

در اینجا مثالی از کاری که باید انجام دهید آورده شده است:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

توابع رویداد-محورِ قابل امتحان مجدد را بی‌اثر کنید

توابع رویدادمحور که می‌توانند دوباره اجرا شوند باید خودتوان (idempotent) باشند. در اینجا چند دستورالعمل کلی برای خودتوان کردن چنین تابعی آورده شده است:

  • بسیاری از APIهای خارجی (مانند Stripe) به شما اجازه می‌دهند یک کلید idempotency را به عنوان پارامتر ارائه دهید. اگر از چنین API استفاده می‌کنید، باید از شناسه رویداد به عنوان کلید idempotency استفاده کنید.
  • قابلیت خودتوانی (Idempotency) با تحویل حداقل یک‌باره به خوبی کار می‌کند، زیرا تلاش مجدد را ایمن می‌کند. بنابراین، یک روش کلی برای نوشتن کد قابل اعتماد، ترکیب قابلیت خودتوانی با تلاش‌های مجدد است.
  • مطمئن شوید که کد شما از نظر داخلی خود-توان (Idempotent) است. برای مثال:
    • مطمئن شوید که جهش‌ها می‌توانند بیش از یک بار اتفاق بیفتند بدون اینکه نتیجه تغییر کند.
    • قبل از تغییر وضعیت، وضعیت پایگاه داده را در یک تراکنش جستجو کنید.
    • مطمئن شوید که همه عوارض جانبی خودشان هماندپوتنت هستند.
  • یک بررسی تراکنشی را خارج از تابع و مستقل از کد اعمال کنید. برای مثال، در جایی حالتی را ثبت کنید که نشان دهد یک شناسه رویداد مشخص قبلاً پردازش شده است.
  • با فراخوانی‌های تکراری توابع خارج از محدوده مقابله کنید. برای مثال، یک فرآیند پاکسازی جداگانه داشته باشید که پس از فراخوانی‌های تکراری توابع، آنها را پاکسازی کند.

پیکربندی سیاست تلاش مجدد

بسته به نیازهای تابع شما، ممکن است بخواهید سیاست تلاش مجدد را مستقیماً پیکربندی کنید. این به شما امکان می‌دهد هر ترکیبی از موارد زیر را تنظیم کنید:

  • کوتاه کردن بازه زمانی تلاش مجدد از ۷ روز به ۱۰ دقیقه.
  • حداقل و حداکثر زمان برگشت را برای استراتژی تلاش مجدد برگشت نمایی تغییر دهید.
  • استراتژی تلاش مجدد را به تلاش مجدد فوری تغییر دهید.
  • یک موضوع منسوخ‌شده (dead-letter topic) پیکربندی کنید.
  • حداکثر و حداقل تعداد تلاش برای تحویل را تنظیم کنید.

برای پیکربندی سیاست تلاش مجدد:

  1. یک تابع HTTP بنویسید.
  2. از API Pub/Sub برای ایجاد اشتراک Pub/Sub استفاده کنید و URL تابع را به عنوان هدف مشخص کنید.

برای اطلاعات بیشتر در مورد پیکربندی مستقیم Pub/Sub، به مستندات Pub/ Pub/Sub Pub/Sub مورد مدیریت خطاها مراجعه کنید.