قوائم الانتظار ومعالجة المهام في 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 قابلة للتوسع وذات أداء عالٍ. في الدرس التالي، سنستكشف جدولة المهام لتشغيل المهام الآلية في فترات زمنية محددة.