لماذا Queues؟
أي عملية لا تحتاجها استجابة HTTP الفورية يجب أن تُوضع في Queue: إرسال البريد الإلكتروني، معالجة الصور، إنشاء التقارير، المزامنة مع واجهات API الخارجية، وإرسال Webhooks. المستخدم لا يحتاج إلى الانتظار لإتمام كل هذا.
الطلب النموذجي الذي يتضمن إرسال بريد إلكتروني يستغرق من 2 إلى 5 ثوانٍ. ضع البريد في Queue وستكون الاستجابة فورية.
تصميم Jobs جيدة
الـ Job المصممة جيدًا تكون صغيرة، وقابلة لإعادة التنفيذ (idempotent)، وقابلة للتسلسل (serializable).
// Good: small, focused, idempotent
class SendInvoiceEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public readonly int $invoiceId
) {}
public function handle(Mailer $mailer): void
{
$invoice = Invoice::with('customer')->findOrFail($this->invoiceId);
// Idempotency check: don't send twice
if ($invoice->email_sent_at) {
return;
}
$mailer->to($invoice->customer->email)->send(
new InvoiceMail($invoice)
);
$invoice->update(['email_sent_at' => now()]);
}
}
// Bad: stores entire Eloquent model (serialization issues)
class SendInvoiceEmail implements ShouldQueue
{
public function __construct(
public readonly Invoice $invoice // Don't do this for complex models
) {}
}
قاعدة: مرّر المعرّفات (IDs) إلى الـ Jobs، وليس الكائنات الكاملة. هذا يتجنب مشاكل التسلسل مع النماذج الكبيرة ويضمن أن الـ Job تعمل دائمًا مع أحدث البيانات.
استراتيجيات إعادة المحاولة
الاتصالات الشبكية تفشل. واجهات API تتعطل. قواعد البيانات تمر بلحظات انقطاع مؤقتة. استراتيجية إعادة المحاولة الخاصة بك تحدد مدى قدرة نظامك على التعامل مع هذه المشكلات بسلاسة.
class SyncOrderToErp implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public int $maxExceptions = 3;
// Exponential backoff: 10s, 30s, 90s, 270s, 810s
public function backoff(): array
{
return [10, 30, 90, 270, 810];
}
public function handle(ErpClient $erp): void
{
$order = Order::findOrFail($this->orderId);
$erp->syncOrder($order->toErpFormat());
}
// Called when all retries are exhausted
public function failed(Throwable $exception): void
{
Log::critical('ERP sync permanently failed', [
'order_id' => $this->orderId,
'error' => $exception->getMessage(),
]);
Notification::route('slack', config('services.slack.alerts'))
->notify(new ErpSyncFailed($this->orderId, $exception));
}
}
تحديد المعدل (Rate Limiting)
عند استدعاء واجهات API خارجية، يجب أن تحترم حدود المعدل الخاصة بها:
class CallExternalApi implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function middleware(): array
{
return [
new RateLimited('external-api'),
];
}
public function handle(): void
{
// Your API call
}
}
// In AppServiceProvider
RateLimiter::for('external-api', function (object $job) {
return Limit::perMinute(30); // Max 30 API calls per minute
});
تجميع الـ Jobs (Job Batching)
عندما تحتاج إلى معالجة عناصر كثيرة وتتبع التقدم الإجمالي:
// Dispatch a batch of jobs
$batch = Bus::batch([
new ProcessRow($rows[0]),
new ProcessRow($rows[1]),
new ProcessRow($rows[2]),
// ... hundreds of jobs
])
->then(function (Batch $batch) {
// All jobs completed successfully
Notification::send($user, new ImportComplete($batch->id));
})
->catch(function (Batch $batch, Throwable $e) {
// First job failure
Log::error("Batch {$batch->id} had a failure: {$e->getMessage()}");
})
->finally(function (Batch $batch) {
// Batch finished (regardless of success/failure)
ImportJob::where('batch_id', $batch->id)->update(['status' => 'finished']);
})
->name('CSV Import')
->allowFailures()
->dispatch();
// Check progress
$batch = Bus::findBatch($batchId);
echo $batch->progress(); // 67 (percent)
تسلسل الـ Jobs (Job Chaining)
عندما يجب أن تعمل الـ Jobs بترتيب تسلسلي:
Bus::chain([
new ExtractDataFromCsv($uploadId),
new ValidateExtractedData($uploadId),
new ImportToDatabase($uploadId),
new SendImportReport($uploadId),
])->dispatch();
اختيار الـ Queue المناسبة
ليست كل الـ Jobs بنفس الأهمية. استخدم عدة Queues بأولويات مختلفة:
// Critical: payment webhooks
ProcessPaymentWebhook::dispatch($event)->onQueue('critical');
// Default: most jobs
SendWelcomeEmail::dispatch($user)->onQueue('default');
// Low priority: analytics, reports
GenerateMonthlyReport::dispatch($month)->onQueue('low');
// Run workers with priority
// php artisan queue:work --queue=critical,default,low
المراقبة في بيئة الإنتاج
الـ Queues تفشل بصمت إذا لم تكن تراقبها. المراقبة الأساسية تشمل:
- عمق الـ Queue: كم عدد الـ Jobs المنتظرة؟ إذا كان العدد يتزايد، فإن الـ Workers لا تستطيع مواكبة الحمل
- الـ Jobs الفاشلة: تحقق من جدول
failed_jobsيوميًا. قم بإعداد تنبيهات. - مدة تنفيذ الـ Job: راقب المدة التي تستغرقها كل Job. الارتفاع المفاجئ يعني أن شيئًا ما تغيّر.
- صحة الـ Workers: استخدم مديري العمليات مثل Supervisor لإعادة تشغيل الـ Workers المتعطلة
// Supervisor config for production
[program:laravel-worker-critical]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --queue=critical --sleep=1 --tries=3
autostart=true
autorestart=true
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/worker-critical.log
[program:laravel-worker-default]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --queue=default,low --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/worker-default.log