home / blog / الغوص العميق في طوابير Laravel: من الأسا...
لارافيل Feb 06, 2026

الغوص العميق في طوابير Laravel: من الأساسيات إلى أنماط الإنتاج

كل ما تحتاج معرفته عن طوابير Laravel — تصميم المهام، استراتيجيات إعادة المحاولة، تحديد المعدل، المعالجة المجمعة، والمراقبة في الإنتاج.

الغوص العميق في طوابير Laravel: من الأساسيات إلى أنماط الإنتاج

لماذا 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
العودة لجميع المقالات