طوابير الانتظار (Queues) في Laravel

من موسوعة حسوب

مقدمة

ملاحظة: يوفر Laravel الآن Horizon، لوحة تحكم وضبط للأنظمة المدعومة من طوابير انتظار Redis. تفقد توثيق Horizon لمزيد من المعلومات.

توفّر طوابير انتظار Laravel واجهة API موحدة بين مختلف طوابير انتظار البيئة الخلفية مثل Beanstalk أو Amazon SQS أو Redis أو قواعد البيانات العلائقية. تسمح ٌطوابير الانتظار بتأخير إنجاز المهام التي تتطلب الكثير من الوقت مثل إرسال بريد لوقت لاحق. تأخير هذه المهام يسرّع طلبات الويب في التطبيق بشكل كبير.

يوجد ملف ضبط طوابير الانتظار في config/queue.php. ستجد في هذا الملف ضبط صلة كل طابور انتظار مضمّنة في الإطار. والتي تتضمن قاعدة بيانات Beanstalkd أو Amazon SQS أو Redis ومشغل تزامني لتنفيذ المهام آنيًا (للاستعمال المحلي). يوجد أيضًا مشغل طوابير الانتظار null لتعطيل تنفيذ المهام المضافة للطابور.

مقارنة الصلة مع طابور الانتظار

قبل بدء العمل بطوابير انتظار Laravel، من المهم فهم الفرق بين طابور الانتظار "queue" والصلة "connection". يوجد في الملف config/queue.php خيار الضبط connections. يعرّف هذا الخيار صلة لخدمة خلفية مثل Amazon SQS أو Beanstalk أو Redis. لكن، يمكن أن تحتوي كل صلة على عدّة طوابير انتظار "queues" التي يمكن اعتبارها مكدس (stack) من المهام المنتظِرة.

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

// إرسال مهمة دون ذكر الطابور
 Job::dispatch();

// إرسال المهمة لطابور محدد
 Job::dispatch()->onQueue('emails');

قد لا تحتاج بعض التطبيقات لإرسال مهام لطوابير متعدد أبدًا بل تفضّل وجود طابور واحد. لكن يكون تعدد طوابير الانتظار مفيدًا للتطبيقات التي تريد إعطاء أولوية أو تقسيم معالجة المهام إذ تسمح عمّال Laravel بتحديد أي طابور انتظار يجب معالجتها و بأي أولوية. مثلًا، إذا أرسلت مهام لطابور الانتظار high، يمكنك إطلاق عامل بأولوية معالجة مرتفعة:

php artisan queue:work --queue=high,default

معلومات برنامج التشغيل و المتطلبات

Database

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

php artisan queue:table

php artisan migrate

Redis

لاستعمال برنامج تشغيل طوابير الانتظار Redis، يجب ضبط صلة قاعدة بيانات Redis في ملف الضبط config/database.php.

Redis Cluster

إذا كانت صلة Redis تستعمل تجميعات Redis Cluster، يجب أن يحتوي اسم الطابور على مفتاح hash tag. هذا مطلوب للتأكد من أن كل المفاتيح المتوفّرة موجودة في نفس المكان:

'redis' => [

   'driver' => 'redis',
   'connection' => 'default',
   'queue' => '{default}',
   'retry_after' => 90,
],
الإيقاف

عند استعمال طوابير انتظار Redis، يمكن استعمال خيار الضبط block_for لتحديد المدة التي يجب أن ينتظرها المشغل لتصبح المهمة متوفرة قبل أن يواصل بقية العملة ويعيد سحب قاعدة بيانات Redis.

قد يكون تعديل هذه القيمة حسب حجم طابور الانتظار أكثر فاعلية من السحب المتواصل لمهام جديدة من قاعدة بيانات Redis. يمكنك مثلا إعطاؤها القيمة 5 لإيقاف المشغل 5 ثوان في حين ينتظر أن تصبح المهمة متوفرة:

'redis' => [

   'driver' => 'redis',
   'connection' => 'default',
   'queue' => 'default',
   'retry_after' => 90,
   'block_for' => 5,
],

تنبيه: Blocking pop هي خاصية تجريبية. يوجد إمكانية بسيطة بأن تضيع مهمة في خادم Redis أو أن يتوقف عامل عن العمل وقت استرجاع المهمة.

متطلبات برامج تشغيل أخرى

تحتاج التبعيات التالية لبرامج التشغيل المذكورة:

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~3.0
  • Redis: predis/predis ~1.0

إنشاء المهام

توليد أصناف المهام

في العادة، توجد كل المهام التي يمكن إضافتها لطوابير الانتظار في المجلد app/Jobs. إذا لم يكن المجلد موجودًا فسينشَأ عند تنفيذ الأمر make:job. يمكنك إحداث مهمّة جديدة باستخدام سطر الأوامر:

php artisan make:job ProcessPodcast

سيُنفّذ الصنفُ المُنشَأ الواجهةَ Illuminate\Contracts\Queue\ShouldQueue، ويُعلم Laravel أنّ المهمة يجب أن تضاف لقامة انتظار وتُنفّذ بطريقة غير متزامنة.

هيكلة الصنف

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

<?php

namespace App\Jobs;

use App\Podcast; use App\AudioProcessor; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable;

class ProcessPodcast implements ShouldQueue {

   use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
   protected $podcast;
   /**
    * إنشاء المهمة
    *
    * @param  Podcast  $podcast
    * @return void
    */
   public function __construct(Podcast $podcast)
   {
       $this->podcast = $podcast;
   }
   /**
    * تنفيذ المهمة.
    *
    * @param  AudioProcessor  $processor
    * @return void
    */
   public function handle(AudioProcessor $processor)
   {
      // معالجة الملفات المرفوعة.
   }
}

في هذا المثال، لاحظ أنه يمكننا تمرير نموذج Eloquent مباشرة للتابع الباني للمهمة. بسبب الخاصية SerializeModels التي تستعملها المهمة، تكون نماذج Eloquent متسلسلة أو غير متسلسلة بلباقة عند معالجة المهمّة. إذا كانت المهمّة المنتظِرة تقبل نموذجًا في التابع الباني، ستتم سلسلة معرّف النموذج فقط في طابور الانتظار. عند التعامل الفعلي مع المهمة، يسترجع النظام تلقائيًا كامل النموذج من قاعدة البيانات. كل هذا واضح للتطبيق ويوقف المشاكل التي يمكن أن تحصل من سلسلة كامل النموذج.

يُنادى التابع handle عند معالجة المهمة من قبل طابور الانتظار. لاحظ أنّه يمكنك تقريب أنواع التبعيات في التابع handle. يضيف حاوي خدمات Laravel تلقائيا هذه التبعيات.

تنبيه: يجب أن تمرّر البيانات الثنائية (binary) مثل الصور الخام عبر الدالة ase64_encode قبل تمريرها لمهمة منتظِرة وإلّا قد لا تتم سَلسلتها لشيفرة JSON بطريقة صحيحة عند إضافتها لطابور الانتظار.

إرسال المهام

بعد كتابة صنف المهمة، يمكنك ارسالها باستعمال التابع dispatch على المهمة نفسها. المعاملات الممررة للتابع dispatch ستمرّر للتابع الباني للمهمة:

<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast; use Illuminate\Http\Request; use App\Http\Controllers\Controller;

class PodcastController extends Controller {

   /**
    * حفظ تدوينة جديدة
    *
    * @param  Request  $request
    * @return Response
    */
   public function store(Request $request)
   {
      // Create podcast...
       ProcessPodcast::dispatch($podcast);
   }
}

الإرسال المتأخر

إذا أردت تأخير تنفيذ مهمّة في طابور الانتظار، يمكنك استعمال التابع delay عند إرسال المهمّة. مثلًا، لنحدد أن المهمة يجب ألّا تكون متوافرة إلا بعد 10 دقائق بعد إرسالها:

<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast; use Illuminate\Http\Request; use App\Http\Controllers\Controller;

class PodcastController extends Controller {

   /**
    * حفظ تدوينة جديدة.
    *
    * @param  Request  $request
    * @return Response
    */
   public function store(Request $request)
   {
      // Create podcast...
       ProcessPodcast::dispatch($podcast)
               ->delay(now()->addMinutes(10));
   }
}

تنبيه: خدمة Amazon SQS لديها حد تأخير أقصى يساوي 15 دقيقة.

تسلسل المهام

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

ProcessPodcast::withChain([

   new OptimizePodcast,
   new ReleasePodcast
])->dispatch();

اتصال السلسلة وطابور الانتظار

إذا أردت تعريف اتصال وطابور انتظار أولي لسلسلة المهام، يمكن استعمال التابعين allOnConnection و allOnQueue. يحدد هذان التابعان الاتصال و طابور الانتظار الواجب استعمالها في حالة عدم تحديد الاتصال و طابور الانتظار فعليا:

ProcessPodcast::withChain([

   new OptimizePodcast,
   new ReleasePodcast
])->dispatch()->allOnConnection('redis')->allOnQueue('podcasts');

تخصيص الصلة و طابور الانتظار

الإرسال لطابور انتظار معين

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

<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast; use Illuminate\Http\Request; use App\Http\Controllers\Controller;

class PodcastController extends Controller {

   /**
    * حفظ تدوينة جديدة
    *
    * @param  Request  $request
    * @return Response
    */
   public function store(Request $request)
   {
      // Create podcast...
       ProcessPodcast::dispatch($podcast)->onConnection('sqs');
   }
}

الإرسال لاتصال معيّن

في حال كنت تتعامل مع اتصالات طوابير انتظا مختلفة، يمكنك تحديد الاتصال لإرسال مهمّة ما وذلك باستخدام التابع onConnection:

<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PodcastController extends Controller
{
    /**
     * حفظ تدوينة جديدة
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // إنشاء تدوينة

        ProcessPodcast::dispatch($podcast)->onConnection('sqs');
    }
}

بالطبع يمكنك سَلسلة onConnection و onQueue لتخصيص الصلة و الطابور:

ProcessPodcast::dispatch($podcast)

             ->onConnection('sqs')
             ->onQueue('processing');

تحديد العدد الأقصى للمحاولات ومدة الانتظار

الحد الأقصى للمحاولات

إحدى الطرائق لتحديد العدد الأقصى للمحاولات تنفيذ مهمّة هي استعمال الخيار tries-- مع أمر artisan:

php artisan queue:work --tries=3

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

<?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue {

   /**
    * العدد الأقصى للمحاولات.
    *
    * @var int
    */
   public $tries = 5;
}

المحاولات على أساس الوقت

كبديل لتحديد العدد الأقصى لمحاولات تنفيذ مهمة قبل إعلان فاشلها، يمكن تحديد الوقت الذي تفشل المهمة إذا تجاوزته. يسمح هذا للمهمة بمحاولة التنفيذ أي عدد من المرات في الوقت المحدد. لتعريف المدة الانتظار، أضف التابع retryUntil لصنف المهمة:

/**

* تحديد زمن الانتظار.
*
* @return \DateTime
*/
public function retryUntil() {

   return now()->addSeconds(5);
}

ملاحظة: يمكنك أيضًا تعريف retryUntil في مستمع أحداث الإضافة لطابور الانتظار.

نفاذ الوقت Timeout

تنبيه: الخاصية timeout تعمل بطريقة أمثل مع PHP 7.1+‎ والإضافة pcntl.

يمكن أيضًا تحديد عدد ثواني الانتظار باستخدام الخيار timeout مع أمر artisan:

php artisan queue:work --timeout=30

لكن، يمكن أيضًا تحديد عدد ثواني الانتظار في صنف المهمة نفسه. إذا حُدد timeout في صنف المهمة تكون له الأولوية على القيمة المحددة في سطر الأوامر:

<?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue {

   /**
    *عدد ثواني العمل قبل إنهاء المهمة.
    *
    * @var int
    */
   public $timeout = 120;
}

تحديد معدل التنفيذ

تنبيه: تتطلب هذه الخاصية أن يتعامل التطبيق مع خادم Redis.

إذا كان التطبيق يتعامل مع خادم Redis، يمكنك ترتيب المهام المنتظِرة حسب الوقت أو التنافسية. تساعد هذه الخاصية خاصة عندما تتعامل المهام مع واجهات API هي أيضًا محدودة. مثلًا، باستخدام التابع throttle، يمكنك تحديد أن لا يتجاوز تنفيذ نوع معين من المهام 10 مرات كل 60 ثانية. إذا كان الحصول على قفلٍ غيرُ ممكنٍ، يمكنك تحرير المهمة لتعاد محاولة تنفيذها لاحقًا:

Redis::throttle('key')->allow(10)->every(60)->then(function () {

  // Job logic...
}, function () {

  // لا يمكن الحصول على قفل..
   return $this->release(10);
});

ملاحظة: في المثال السابق، يمكن أن تكون قيمة key أي سلسلة حروف تعرف بصفة فريدة نوع المهمة تريد تحديد معدّل تنفيذها. مثلًا، قد تريد صناعة المفتاح باستخدام اسم صنف المهمة ومعرّف النموذج الذي تعمل عليه. بدل ذلك، يمكنك تحديد العدد الأقصى للعمّال المتوازين على نفس المهمة. تكون هذه الخاصية مفيدة عند العمل مع موارد لا تقبل أكثر من مهمة واحدة في الوقت. مثلاً، استخدم التابع funnel لتحديد نوع المهام التي يجب تنفيذها من قبل عامل واحد:

Redis::funnel('key')->limit(1)->then(function () {

  // Job logic...
}, function () {

  // Could not obtain lock...
   return $this->release(10);
});

ملاحظة: عند استعمال تحديد التنفيذ، قد يكون من الصعب تحديد عدد المحاولات اللازمة لإنجاح المهمة بدقة. لذلك من المفيد دمج تحديد التنفيذ مع المحاولات المقيدة بوقت.

التعامل مع الأخطاء

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

تشغيل عمّال الطابور

يتضمّن Laravel عامل طوابير انتظار يعالج المهم الجديدة عند دفعها للطابور. يمكن تشغيل عامل بتنفيذ الأمر queue:work. لاحظ أنّه عند بدء الأمر، سيواصل العمل إلى أن يتم إيقافه يدويًا أو إغلاق الطرفية:

php artisan queue:work

ملاحظة: لإبقاء معالج الأمر queue:work يعمل بطريقة دائمة في الخلفية، يجب عليك استخدام مراقب معالجات مثل supervisor للتأكد من أن المعالج لم يتوقف فجأة. تذكر أن العمّال معالجاتٌ تعيش طويلًا وتسجّل حالة التطبيق في الذاكرة و بالتالي لن تلاحظ أي تغيير في التطبيق بعد إطلاقها، لذا تأكد من إعادة إطلاقها بعد نشر التطبيق.

معالجة مهمة واحدة

يُستعمل الخيار once-- لإخبار العامل بمعالجة مهمة واحدة من طابور الانتظار:

php artisan queue:work --once

تخصيص اتصال وطابور انتظار

يمكنك تحديد أي اتصال على العامل استخدامه. يجب أن يتوافق اسم الاتصال الممرّر للأمر work مع إحدى الصّلات المعرفة في ملف الضبط config/queue.php:

php artisan queue:work redis

يمكنك تخصيص العامل أكثر عبر معالجة طوابير معيّنة فقط من الاتصال. مثلًا، إذا كانت كل رسائل البريد الإلكتروني تُعالَج في طابور انتظار واحد emails من الصلة redis، يمكنك إطلاق عامل يعالج فقط ذلك الطابور باستعمال الأمر التالي:

php artisan queue:work redis --queue=emails

مراعاة الموارد

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

أولويات طوابير الانتظار

قد تريد في بعض الأحيان إعطاء أولوية لمعالجة طوابير الانتظار. مثلًا، في ملف الضبط config/queue.php يمكنك أن أن تضبط الطابور الأولي للصلة redis بالقيمة low لكن فد تريد في بعض الأحيان إضافة مهمة لطابور أعلى أولوية high كالآتي:

dispatch((new Job)->onQueue('high'));

لإطلاق عامل يتثبت من أن كل المهام ذات أولوية مرتفعة high قد عولجت قبل المواصلة لبقية المهام low، مرّر لائحة من أسماء طوابير الانتظار مفرّقف بفاصلة ',' إلى الأمر work:

php artisan queue:work --queue=high,low

عملة طوابير الانتظار والنشر على الخادم الإنتاجي

حيث أن العمّال معالجات تعيش طويلا، لن تلاحظ تغييرات في الشيفرة إن لم تتم إعادة إطلاقها. لذا، الطريقة الأسهل نشر تطبيق يستعمل عمّال الطوابير هي إعادة إطلاق العمّال في عملية النشر. يمكنك إعادة نشر العمال بسهولة باستخدام الأمر queue:restart:

php artisan queue:restart

سيطلب هذا الأمر من العمال جميعًا الانتهاء بعد إنهاء المهمة الحالية حتى لا تضيع أي مهمة موجودة. حيث سينتهي العمال الموجودون بعد الأمر queue:restart، يجب أن تملك مراقب معالجات مثل Supervisor ليعيد إطلاقهم آليًا.

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

صلاحية المهام ونفاذ الوقت

إنتهاء صلاحية المهام

في ملف config/queue.php، تعرّف كل صلة الخيار retry_after. يحدّد هذا الخيار عدد الثواني الذي يجب أن تنتظرها الصلة قبل محاولة إعادة تنفيذ مهمة تتم معالجتها. مثلًا، إن كانت قيمة retry_after تساوي 90، تُعاد المهمة للطابور إذا تمت معالجتها لمدة 90 ثانية دون حذفها. في العادة، يجب ضبط قيمة retry_after للمدة المعقولة التي تستغرقها المهمة لإكمال المعالجة.

تنبيه: الصلة الوحيدة التي لا تتضمن retry_after هي Amazon SQS. يعيد SQS محاولة التنفيذ حسب الوقت المبدئي للرؤية الذي يحدد في سطر أوامر AWS.

وقت نفاذ العامل

يملك الأمر queue:work الخيار timeout--. يحدد هذا الخيار المدة التي ينتظرها المعالج الرئيسي لطوابير الانتظار في Laravel قبل أن ينهي عامل طابور انتظار فرعية بصدد معالجة مهمة. في بعض الأحيان، يصبح عامل إحدى الطوابير الفرعية متجمّدا "frozen" لعدة أسباب، كنداء HTTP خارجي غير متجاوب. يحذف الخيار timeout-- المعالجات المتجمدة التي تتجاوز المدة المحددة في الخيار:

php artisan queue:work --timeout=60

خيار الضبط retry_after وخيار الأمر timeout-- مختلفان، لكن يعملان معًا للتأكد من أن المهمة لا تضيع ولا تعالج بطريقة ناجحة إلا مرّة واحدة.

تنبيه: قيمة timeout-- يجب أن تكون دومًا أقصر بعدّة ثواني على الأقل من قيمة retry_after. سيضمن هذا أن العامل الذي يعالج مهمة معينة ينتهي قبل إعادة محاولة المهمة. إذا كانت مدة timeout-- أطول من retry_after قد تتم معالجة المهمة مرتين.

مدة نوم العامل

عندما تتوفر مهام في طابور الانتظار يواصل العامل العمل دون تأخير بينها. لكن، يحدد الخيار sleep مدة نوم العامل (بالثواني) في حالة عدم وجود مهام متوفرة. عند نومه، لن يعالج العامل أي مهام حتى يستيقظ مجددًا:

php artisan queue:work --sleep=3

ضبط Supervisor

تثبيت Supervisor

Supervisor مراقب معالجات لنظام لينكس وسيعيد إطلاق المعالجات آليًا في حال فشلها. لتثبيت Supervisor، استعمل الأمر التالي:

sudo apt-get install supervisor

ملاحظة: إذا كان ضبط supervisor بمفردك يبدو صعبا، فكر في استعمال Laravel forge الذي يثبت ويضبط supervisor تلقائيًا لتطبيقات Laravel.

ضبط Supervisor

توجد ملفات ضبط supervisor عادة في المجلد etc/supervisor/conf.d/. في هذا المجلد يمكنك أن تُنشِئ أي عدد من ملفات الضبط لإخبار supervisor كيف يراقب معالجاتك. مثلًا، لننشِئ الملف laravel-worker.conf الذي يُطلق و يراقب معالجات queue:work:

[program:laravel-worker]

process_name=%(program_name)s_%(process_num)02d

command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 autostart=true autorestart=true user=forge numprocs=8 redirect_stderr=true 

stdout_logfile=/home/forge/app.com/worker.log

في هذا المثال، ستطلب التعليمة numprocs من المراقب supervisor إطلاق 8 معالجات queue:work ومراقبتها كلها، وإعادة إطلاقها آليًا إن فشلت. طبعًا، يجب أن تغير queue:work sqs في التعليمة command للصلة التي تريد استخدامها.

تشغيل Supervisor

بعد صناعة ملف الضبط، يمكنك تحيين ضبط supervisor وتشغيله باستخدام الأوامر التالية:

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start laravel-worker:*

للمزيد من المعلومات تفقد توثيق supervisor.

التعامل مع المهام الفاشلة

في بعض الأحيان، تفشل المهام. لا تقلق ، لا تسير الأمور دائما كالمتوقع. يتضمن Laravel طريقة سهلة لتحديد العدد الأقصى لمحاولة تنفيذ مهمة. بعد أن تتجاوز المهمة العدد الأقصى للمحاولات، تُضمَّن في الجدول failed_jobs في قاعدة البيانات. لصناعة ترحيل للجدول failed_jobs يمكن استعمال الأمر queue:failed_jobs:

php artisan queue:failed-table
php artisan migrate

عند تشغيل عمال طوابير الانتظار، يجب أن تحدد العدد الأقصى لمحاولات تنفيذ المهام باستخدام الخيار tries-- مع الأمر queue:work. إذا لم تحدد قيمة للخيار tries--، فستعاد المحاولة دون توقف:

php artisan queue:work redis --tries=3

التنظيف بعد المهام الفاشلة

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

<?php

namespace App\Jobs;

use Exception; use App\Podcast; use App\AudioProcessor; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessPodcast implements ShouldQueue {

   use InteractsWithQueue, Queueable, SerializesModels;
   protected $podcast;
   /**
    * إنشاء نسخة مهمة
    *
    * @param  Podcast  $podcast
    * @return void
    */
   public function __construct(Podcast $podcast)
   {
       $this->podcast = $podcast;
   }
   /**
    * تنفيذ المهمة
    *
    * @param  AudioProcessor  $processor
    * @return void
    */
   public function handle(AudioProcessor $processor)
   {
      // Process uploaded podcast...
   }
   /**
    * فشلت المهمة في المواصلة
    *
    * @param  Exception  $exception
    * @return void
    */
   public function failed(Exception $exception)
   {
      // إرسال إشعار فشل للمستخدم
   }
}

أحداث المهام الفاشلة

إذا أردت تسجيل حدث يُطلق عند فشل مهمة، يمكنك استخدام التابع Queue:failing. يمثل الحدث فرصة قيّمة لإعلام الفريق ببريد إلكتروني أو stride. مثلًا، يمكننا إرفاق نداء لهذا الحدث من AppServiceProvider المُضمَّنة في Laravel:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue; use Illuminate\Queue\Events\JobFailed; use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider {

   /**
    * تمهيد أي خدمات للتطبيق
    *
    * @return void
    */
   public function boot()
   {
       Queue::failing(function (JobFailed $event) {
          // $event->connectionName
          // $event->job
          // $event->exception
       });
   }
   /**
    * تسجيل حاوي الخدمات
    *
    * @return void
    */
   public function register()
   {
      //
   }
}
إعادة تشغيل المهام الفاشلة

لإظهار كل المهام الفاشلة الموجودة في الجدول failed_jobs، استخدم الأمر queue:failed:

php artisan queue:failed

سيُظهر الأمر queue:failed معرَف المهمة، والصلة، وطابور الانتظار، ووقت الفشل. يمكن استعمال معرّف المهمة لإعادة تشغيلها. مثلا، لإعادة تشغيل المهمة الفاشلة ذات المعرَف 5، استعمل الأمر التالي:

php artisan queue:retry 5

لإعادة تشغيل كل المهام الفاشلة استعمل الأمر queue:retry ومرّر all كمعرَف المهمة:

php artisan queue:retry all

إذا أردت حذف مهمة فاشلة، استخدم الأمر queue:forget:

php artisan queue:forget 5

لحذف كل المهام الفاشلة، يمكنك استخدام الأمر queue:flush:

php artisan queue:flush

أحداث المهام

باستخدام التابعين before و after من الواجهة الثابتة Queue، يمكن إنشاء نداء يٌنفّذ قبل أو بعد معالجة مهمة. تمثّل هذه النداءات فرصة جيدة للقيام بتسجيلات إضافية وزيادة الإحصاءات لواجهة التحكم. في العادة، يجب نداء هذه التوابع من مزود الخدمات. مثلًا، يمكنك استخدام AppServiceProvider المضمنة في Laravel:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue; use Illuminate\Support\ServiceProvider; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing;

class AppServiceProvider extends ServiceProvider {

   /**
    * تمهيد أي خدمات للتطبيق
    *
    * @return void
    */
   public function boot()
   {
       Queue::before(function (JobProcessing $event) {
          // $event->connectionName
          // $event->job
          // $event->job->payload()
       });
       Queue::after(function (JobProcessed $event) {
          // $event->connectionName
          // $event->job
          // $event->job->payload()
       });
   }
   /**
    * تسجيل حاوي الخدمات
    *
    * @return void
    */
   public function register()
   {
      //
   }
}

باستخدام التابع looping من الواجهة الساكنة Queue، يمكنك تحديد نداءات قبل أن يحاول العامل إحضار المهمة من طابور الانتظار. مثلًا، يمكن تسجيل Closure لتنفيذ rollback على أي عملية نقل (transaction) غير مكتملة من مهام فاشلة:

Queue::looping(function () {

   while (DB::transactionLevel() > 0) {
       DB::rollBack();
   }
});

مصادر