إطار Laravel

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

18 دقيقة الدرس 18 من 45

قوائم الانتظار ومعالجة المهام في Laravel

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

تكوين قوائم الانتظار

قم بتكوين برنامج تشغيل قائمة الانتظار في ملف .env:

# برنامج تشغيل Sync (يعمل فورًا، للتطوير) QUEUE_CONNECTION=sync # برنامج تشغيل قاعدة البيانات (يخزن المهام في قاعدة البيانات) QUEUE_CONNECTION=database # برنامج تشغيل Redis (موصى به للإنتاج) QUEUE_CONNECTION=redis REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 # Amazon SQS QUEUE_CONNECTION=sqs AWS_ACCESS_KEY_ID=your-key-id AWS_SECRET_ACCESS_KEY=your-secret-key AWS_DEFAULT_REGION=us-east-1 SQS_QUEUE=your-queue-url # Beanstalkd QUEUE_CONNECTION=beanstalkd BEANSTALKD_HOST=127.0.0.1 BEANSTALKD_QUEUE=default

لقائمة انتظار قاعدة البيانات، قم بإنشاء جدول المهام:

php artisan queue:table php artisan migrate

لتتبع المهام الفاشلة، قم بإنشاء جدول المهام الفاشلة:

php artisan queue:failed-table php artisan migrate
ملاحظة: يُوصى باستخدام Redis لبيئات الإنتاج نظرًا لسرعته وموثوقيته. قم بتثبيت حزمة predis/predis: composer require predis/predis

إنشاء المهام

قم بإنشاء فئة مهمة باستخدام Artisan:

php artisan make:job ProcessPodcast php artisan make:job SendEmailNotification php artisan make:job GenerateInvoice

مثال على فئة مهمة:

<?php namespace App\Jobs; use App\Models\Podcast; use App\Services\AudioProcessor; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class ProcessPodcast implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $podcast; // عدد مرات محاولة المهمة public $tries = 3; // الحد الأقصى للوقت بالثواني قبل انتهاء المهلة public $timeout = 120; // الحد الأقصى لعدد الاستثناءات قبل الفشل public $maxExceptions = 3; // حذف المهمة إذا كانت النماذج مفقودة public $deleteWhenMissingModels = true; public function __construct(Podcast $podcast) { $this->podcast = $podcast; } public function handle(AudioProcessor $processor) { // معالجة صوت البودكاست $processor->process($this->podcast); // تحديث الحالة $this->podcast->update([ 'status' => 'processed', 'processed_at' => now(), ]); } // معالجة فشل المهمة public function failed(\Throwable $exception) { $this->podcast->update(['status' => 'failed']); // إخطار المسؤول \Log::error('فشلت معالجة البودكاست', [ 'podcast_id' => $this->podcast->id, 'error' => $exception->getMessage(), ]); } }

إرسال المهام

أرسل المهام إلى قائمة الانتظار باستخدام طرق متعددة:

use App\Jobs\ProcessPodcast; // إرسال إلى قائمة الانتظار الافتراضية ProcessPodcast::dispatch($podcast); // إرسال إذا كان الشرط صحيحًا ProcessPodcast::dispatchIf($podcast->should_process, $podcast); // إرسال ما لم يكن الشرط صحيحًا ProcessPodcast::dispatchUnless($podcast->already_processed, $podcast); // إرسال بعد التزام معاملة قاعدة البيانات ProcessPodcast::dispatch($podcast)->afterCommit(); // تأخير تنفيذ المهمة ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10)); // تحديد اسم قائمة الانتظار ProcessPodcast::dispatch($podcast)->onQueue('processing'); // تحديد اتصال قائمة الانتظار ProcessPodcast::dispatch($podcast)->onConnection('redis'); // سلسلة المهام (التشغيل المتسلسل) ProcessPodcast::withChain([ new OptimizePodcast($podcast), new ReleasePodcast($podcast), ])->dispatch($podcast); // إرسال متزامن (غير موضوع في قائمة انتظار) ProcessPodcast::dispatchSync($podcast); // إرسال بعد إرسال الاستجابة للمستخدم ProcessPodcast::dispatchAfterResponse($podcast);

وسيط المهام

يسمح لك وسيط المهام بلف منطق مخصص حول تنفيذ المهمة:

php artisan make:middleware RateLimited

إنشاء وسيط محدد المعدل:

<?php namespace App\Jobs\Middleware; use Illuminate\Support\Facades\Redis; class RateLimited { public function handle($job, $next) { Redis::throttle('key') ->block(0) ->allow(10) ->every(60) ->then(function () use ($job, $next) { $next($job); }, function () use ($job) { // إعادة المهمة إلى قائمة الانتظار مع تأخير $job->release(10); }); } }

تطبيق الوسيط على المهمة:

<?php use App\Jobs\Middleware\RateLimited; class ProcessPodcast implements ShouldQueue { public function middleware() { return [new RateLimited]; } // أو استخدم الوسيط المدمج public function middleware() { return [ (new WithoutOverlapping($this->podcast->id)) ->releaseAfter(60) ->expireAfter(180), ]; } }

دفعات المهام

معالجة مهام متعددة كدفعة وتتبع تقدمها الجماعي:

use Illuminate\Bus\Batch; use Illuminate\Support\Facades\Bus; $batch = Bus::batch([ new ProcessPodcast($podcast1), new ProcessPodcast($podcast2), new ProcessPodcast($podcast3), ])->then(function (Batch $batch) { // اكتملت جميع المهام بنجاح \Log::info('تمت معالجة جميع البودكاست'); })->catch(function (Batch $batch, \Throwable $e) { // تم اكتشاف فشل أول مهمة في الدفعة \Log::error('فشلت معالجة الدفعة', ['error' => $e->getMessage()]); })->finally(function (Batch $batch) { // انتهت الدفعة من التنفيذ \Log::info('انتهت الدفعة'); })->name('معالجة البودكاست') ->onConnection('redis') ->onQueue('processing') ->dispatch(); // الحصول على معرف الدفعة $batchId = $batch->id; // فحص حالة الدفعة $batch = Bus::findBatch($batchId); $progress = $batch->progress(); // النسبة المئوية للاكتمال $pending = $batch->pendingJobs; $failed = $batch->failedJobs; $finished = $batch->finished(); $cancelled = $batch->cancelled(); // إلغاء الدفعة $batch->cancel(); // إضافة مهام إلى دفعة موجودة $batch->add([ new ProcessPodcast($podcast4), new ProcessPodcast($podcast5), ]);
نصيحة: أنشئ جدول job_batches قبل استخدام الدفعات: php artisan queue:batches-table && php artisan migrate

المهام الفاشلة

إدارة المهام الفاشلة باستخدام أوامر Artisan:

# قائمة جميع المهام الفاشلة php artisan queue:failed # إعادة محاولة مهمة فاشلة محددة php artisan queue:retry 5 # إعادة محاولة جميع المهام الفاشلة php artisan queue:retry all # حذف مهمة فاشلة php artisan queue:forget 5 # حذف جميع المهام الفاشلة php artisan queue:flush # تقليم المهام الفاشلة الأقدم من 48 ساعة php artisan queue:prune-failed --hours=48

مراقبة المهام الفاشلة برمجيًا:

use Illuminate\Support\Facades\Queue; Queue::failing(function (JobFailed $event) { // $event->connectionName // $event->job // $event->exception // إخطار المسؤول \Log::critical('فشلت المهمة', [ 'job' => $event->job->getName(), 'connection' => $event->connectionName, 'exception' => $event->exception->getMessage(), ]); });

عمال قوائم الانتظار

ابدأ عمال قوائم الانتظار لمعالجة المهام:

# معالجة المهام في قائمة الانتظار الافتراضية php artisan queue:work # معالجة اتصال محدد php artisan queue:work redis # معالجة قائمة انتظار محددة php artisan queue:work redis --queue=emails,processing,default # معالجة مهمة واحدة والتوقف php artisan queue:work --once # معالجة المهام لمدة 60 ثانية php artisan queue:work --max-time=60 # معالجة 100 مهمة والتوقف php artisan queue:work --max-jobs=100 # التوقف بعد معالجة المهمة الحالية php artisan queue:work --stop-when-empty # تعيين حد الذاكرة php artisan queue:work --memory=128 # تعيين مهلة المهمة php artisan queue:work --timeout=60 # تأخير المهام الفاشلة php artisan queue:work --backoff=3 # تعيين مدة السكون عندما لا تتوفر مهام php artisan queue:work --sleep=3 # عدد المهام التي يتم معالجتها في وقت واحد php artisan queue:work --tries=3
تحذير: عمال قوائم الانتظار هم عمليات طويلة الأمد. لن يلتقطوا تغييرات الكود بدون إعادة التشغيل. استخدم php artisan queue:restart لإعادة تشغيل جميع العمال بشكل لطيف بعد نشر الكود.

تكوين Supervisor

استخدم Supervisor للحفاظ على تشغيل عمال قوائم الانتظار في الإنتاج:

# /etc/supervisor/conf.d/laravel-worker.conf [program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 autostart=true autorestart=true stopasgroup=true killasgroup=true user=www-data numprocs=8 redirect_stderr=true stdout_logfile=/var/www/html/storage/logs/worker.log stopwaitsecs=3600

أوامر Supervisor:

# إعادة تحميل التكوين sudo supervisorctl reread sudo supervisorctl update # بدء العمال sudo supervisorctl start laravel-worker:* # إيقاف العمال sudo supervisorctl stop laravel-worker:* # إعادة تشغيل العمال sudo supervisorctl restart laravel-worker:* # فحص الحالة sudo supervisorctl status

Laravel Horizon

يوفر Horizon لوحة تحكم ونظام تكوين لقوائم انتظار Redis:

# تثبيت Horizon composer require laravel/horizon # نشر الأصول php artisan horizon:install # بدء Horizon php artisan horizon # إيقاف Horizon مؤقتًا php artisan horizon:pause # متابعة Horizon php artisan horizon:continue # إنهاء Horizon php artisan horizon:terminate

تكوين Horizon في config/horizon.php:

'environments' => [ 'production' => [ 'supervisor-1' => [ 'connection' => 'redis', 'queue' => ['default', 'emails', 'notifications'], 'balance' => 'auto', 'maxProcesses' => 10, 'maxTime' => 0, 'maxJobs' => 0, 'memory' => 128, 'tries' => 3, 'timeout' => 60, ], ], 'local' => [ 'supervisor-1' => [ 'connection' => 'redis', 'queue' => ['default'], 'balance' => 'auto', 'maxProcesses' => 3, 'tries' => 3, ], ], ],
تمرين 1: أنشئ مهمة تعالج الصور المحملة (تغيير الحجم، التحسين، إنشاء صور مصغرة). نفذ معالجة صحيحة للأخطاء ومنطق إعادة المحاولة وتتبع التقدم. سلسل المهام لتحميل الصور المعالجة إلى S3.
تمرين 2: ابنِ نظام بريد إلكتروني جماعي باستخدام دفعات المهام. أنشئ مهمة لإرسال رسائل بريد إلكتروني فردية، ادمجها معًا، ووفر لوحة معلومات تقدم توضح عدد رسائل البريد الإلكتروني التي تم إرسالها أو فشلت أو معلقة.
تمرين 3: نفذ مستخرج API محدود المعدل باستخدام وسيط المهام. يجب أن تجلب المهمة البيانات من واجهة برمجة تطبيقات خارجية، وتحترم حدود المعدل (10 طلبات في الدقيقة)، وتتعامل مع الإخفاقات بلطف مع التراجع الأسي.

قوائم الانتظار ضرورية لبناء تطبيقات Laravel قابلة للتوسع وذات أداء عالٍ. في الدرس التالي، سنستكشف جدولة المهام لتشغيل المهام الآلية في فترات زمنية محددة.