JOURNAL INDEX
2026.09.17/26 VIEWS/5 MIN READ

إعداد Laravel Queue مع Redis و Horizon خطوة بخطوة

عبدالرحمن ربيع
عبدالرحمن ربيع Software Engineer & AI Builder

دليل عملي لإعداد Laravel Queue مع Redis وHorizon، وإنشاء Jobs ومراقبتها وضبط المحاولات والمهل وتشغيل العمال بأمان في الإنتاج.

إعداد Laravel Queue مع Redis و Horizon خطوة بخطوة

لماذا تستخدم Laravel Queue مع Redis وHorizon؟

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

يعمل Redis هنا بوصفه مخزنًا للطوابير، بينما يدير Laravel Horizon العمال ويعرض حالة التنفيذ والمهام الفاشلة ومؤشرات التشغيل. في هذا الدليل سنبني مثالًا لإرسال رسالة ترحيب، ثم نضبط المحاولات والمهل والحماية والنشر. نفترض وجود مشروع Laravel حديث وقاعدة بيانات عاملة، وأن التنفيذ والإنتاج على نظام يدعم عمليات Horizon، مثل Linux.

1. تجهيز Redis والاتصال من Laravel

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

redis-cli ping

يحتاج Laravel إلى عميل Redis. يمكنك استخدام امتداد PhpRedis بعد تثبيته في بيئة PHP، أو تثبيت Predis بواسطة Composer. سنستخدم Predis لتبسيط المثال؛ لا تحتاج إلى تثبيت العميلين معًا. تأكد أيضًا من توفر امتدادي pcntl وposix في PHP CLI الذي يشغّل Horizon.

composer require predis/predis
php -m

عدّل ملف البيئة بالقيم المناسبة لخادمك. اسم اتصال Redis داخل إعدادات الطابور يختلف عن اسم برنامج تشغيل الطابور؛ الأول يحدد الخادم، والثاني يحدد آلية التخزين.

QUEUE_CONNECTION=redis
REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
REDIS_QUEUE_CONNECTION=default
REDIS_QUEUE=default
REDIS_QUEUE_RETRY_AFTER=120

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

2. تثبيت Laravel Horizon ونشر إعداداته

ثبّت Horizon داخل المشروع، ثم شغّل أمر التثبيت لنشر الملفات اللازمة. احتفظ به ضمن اعتماديات التشغيل، وليس اعتماديات التطوير فقط، لأن خادم الإنتاج يحتاج إليه لتشغيل العمال. يدعم Horizon طوابير Redis؛ تغيير اتصال الطابور إلى قاعدة بيانات لا يحقق الإعداد الموضح هنا.

composer require laravel/horizon
php artisan horizon:install
php artisan config:clear

راجع ملف config/horizon.php والمزوّد المنشور في التطبيق. قد تختلف بعض القيم الافتراضية بين الإصدارات، لذلك عدّل الأقسام الموجودة بدل استبدال الملف بالكامل. يزيل أمر مسح الإعدادات النسخة المخبأة محليًا حتى يقرأ التطبيق تغييرات البيئة، وسنعيد بناء الكاش عند النشر.

3. ضبط اتصال الطابور وحماية المهام من التنفيذ المبكر

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

'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 120),
    'block_for' => 5,
    'after_commit' => true,
],

قيمة retry_after ليست وقت الانتظار بين المحاولات؛ إنها مدة اعتبار المهمة المحجوزة قيد التنفيذ قبل السماح بإتاحتها مجددًا إذا لم تُحذف بنجاح. أما block_for فيحدد مدة انتظار اتصال Redis لوصول عمل جديد. الانتظار المحدود يساعد العمال على الاستجابة لإشارات الإيقاف بدل الحجب غير المحدود.

4. إنشاء Job حقيقية لإرسال بريد الترحيب

أنشئ المهمة باستخدام Artisan. يعتمد المثال على نموذج User الافتراضي وحقلي name وemail، كما يفترض تجهيز اتصال البريد. أثناء التجربة يمكنك استخدام MAIL_MAILER=log لتسجيل محتوى الرسالة بدل إرسال بريد فعلي، مع الانتباه إلى أن السجلات قد تحتوي بيانات شخصية.

php artisan make:job SendWelcomeEmail

استبدل محتوى ملف المهمة بالتعريف التالي. نخزن معرّف المستخدم فقط، ثم نجلب بياناته عند التنفيذ. إذا حُذف الحساب قبل معالجة المهمة، ننهيها بهدوء بدل تكرار محاولة إرسال رسالة إلى مستخدم غير موجود.

<?php

namespace App\Jobs;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $timeout = 60;

    public function __construct(public int $userId)
    {
    }

    public function backoff(): array
    {
        return [10, 30, 60];
    }

    public function handle(): void
    {
        $user = User::find($this->userId);

        if (! $user) {
            return;
        }

        Mail::raw(
            'Welcome, '.$user->name.'!',
            function ($message) use ($user) {
                $message->to($user->email)
                    ->subject('Welcome to our application');
            }
        );
    }
}

وجود ShouldQueue هو ما يجعل المهمة قابلة للتنفيذ عبر الطابور. لا تخزن كلمات مرور أو ملفات ضخمة داخل بيانات المهمة؛ مرر معرّفًا أو مسارًا آمنًا بدلًا منها. واضبط مهل اتصال مزود البريد أيضًا، لأن مهلة العامل ليست بديلًا عن مهل عمليات الشبكة.

5. إرسال المهمة إلى الطابور الصحيح

يمكن إرسال المهمة من المتحكم بعد تسجيل المستخدم، أو من مستمع حدث. للتجربة السريعة افتح Tinker واستخدم مستخدمًا موجودًا. اخترنا طابور emails لتفصيل أعمال البريد عن المهام العامة؛ يجب أن يراقب Horizon هذا الاسم تحديدًا حتى يعالج الرسائل.

php artisan tinker
$user = App\Models\User::query()->firstOrFail();
App\Jobs\SendWelcomeEmail::dispatch($user->id)
    ->onQueue('emails');

إذا أرسلت المهمة داخل معاملة، فإن after_commit الذي ضبطناه يؤخر إرسالها حتى اعتماد المعاملة، ويتجاهل المهام المؤجلة معها عند التراجع عنها. تجنب dispatchSync في هذا المسار لأنه ينفذ المهمة فورًا داخل العملية الحالية، فلا يحقق هدف نقل العمل خارج الطلب.

6. إعداد مشرف Horizon والمهل

داخل قسم environments في config/horizon.php، اضبط مشرف الإنتاج ليعالج emails وdefault. يحدد connection اتصالًا من config/queue.php، وليس اسم اتصال Redis المباشر من config/database.php. الأرقام التالية نقطة بداية لتطبيق صغير، وليست وصفة ثابتة لجميع الخوادم.

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['emails', 'default'],
            'balance' => 'auto',
            'minProcesses' => 1,
            'maxProcesses' => 5,
            'tries' => 3,
            'timeout' => 90,
            'memory' => 128,
        ],
    ],
    'local' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['emails', 'default'],
            'balance' => 'auto',
            'maxProcesses' => 3,
            'tries' => 3,
            'timeout' => 90,
        ],
    ],
],

في المثال مهلة المهمة 60 ثانية، ومهلة Horizon تساوي 90، وretry_after تساوي 120. اجعل مهلة Horizon أعلى من مهلة المهمة وأقل من retry_after بهامش مناسب. خلاف ذلك قد تصبح المهمة متاحة لعامل آخر بينما التنفيذ الأول لم ينتهِ بعد. وإعداد tries داخل المهمة يتقدم على القيمة العامة للمشرف.

هل ترتيب الطوابير يعني أولوية صارمة؟

لا تعتمد على ترتيب الأسماء مع balance=auto لضمان أولوية مطلقة. يوزع Horizon العمليات بحسب حمل الطوابير. إذا كانت لديك مدفوعات أو إشعارات عاجلة، خصص لها مشرفًا منفصلًا وموارد محسوبة، مع مراقبة استهلاك الذاكرة وحدود الاتصالات بخدماتك الخارجية.

7. تشغيل Horizon وحماية لوحة المراقبة

شغّل Horizon محليًا، ثم افتح مسار /horizon داخل التطبيق. يجب أن ترى المهمة تنتقل إلى الاكتمال عند نجاحها. يعرض أمر الحالة وضع Horizon، لكنه لا يغني عن متابعة معدل الفشل وزمن انتظار المهام فعليًا.

php artisan horizon
php artisan horizon:status

نفذ أمر الحالة في طرفية أخرى لأن أمر التشغيل يبقى في الواجهة. في الإنتاج، لا تجعل لوحة المراقبة عامة؛ فهي تعرض تفاصيل تشغيلية وقد تكشف بيانات المهام. عدّل بوابة viewHorizon في المزوّد المنشور، وتأكد من إعداد المصادقة والمسارات بما يناسب التطبيق.

use Illuminate\Support\Facades\Gate;

protected function gate(): void
{
    Gate::define('viewHorizon', function ($user) {
        return in_array($user->email, [
            'ops@example.com',
        ], true);
    });
}

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

8. التعامل مع المهام الفاشلة والمحاولات

تحقق من إعداد failed داخل config/queue.php ووجود جدول failed_jobs إذا كنت تستخدم تخزين الفشل في قاعدة البيانات. تتضمن مشاريع حديثة ترحيله مسبقًا؛ لا تنشئ ترحيلًا مكررًا. عند غيابه يمكنك توليده بالأمر التالي ثم تشغيل الترحيلات.

php artisan make:queue-failed-table
php artisan migrate
php artisan queue:failed
php artisan queue:retry FAILED_JOB_UUID

استبدل المعرّف بالقيمة المعروضة، وأعد المحاولة بعد إصلاح السبب، لا قبله. الأخطاء المؤقتة تستفيد من backoff، لكن عنوان بريد غير صالح أو إعداد اتصال مفقود يحتاج تصحيحًا. وقد تُحسب عمليات تحرير المهمة بواسطة بعض البرمجيات الوسيطة ضمن المحاولات، لذا راجع سلوكها قبل اختيار حد منخفض.

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

9. إبقاء Horizon قيد التشغيل في الإنتاج

لا تعتمد على جلسة SSH مفتوحة. استخدم مدير عمليات مثل Supervisor لإعادة تشغيل Horizon عند توقفه. المثال التالي يفترض وجود Supervisor ومشروع في /var/www/app؛ عدّل المسارات والمستخدم وفق ملكية الملفات وصلاحيات السجلات والتخزين.

[program:laravel-horizon]
process_name=%(program_name)s
command=/usr/bin/php /var/www/app/artisan horizon
directory=/var/www/app
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/horizon.log
stopwaitsecs=180
stopasgroup=true
killasgroup=true
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-horizon

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

10. النشر وتحديث العمال وقياس الأداء

العمال عمليات طويلة العمر، ولذلك لا يكفي رفع ملفات PHP ليستخدموا الشيفرة الجديدة. بعد تجهيز الإصدار وتشغيل الترحيلات المتوافقة، ابنِ كاش الإعدادات ثم اطلب إنهاء Horizon بأمان. يكمل أعماله الحالية ويخرج، ويعيد مدير العمليات تشغيله بالشيفرة الجديدة.

php artisan config:cache
php artisan horizon:terminate

لجمع لقطات مقاييس Horizon دوريًا، أضف المهمة المجدولة التالية إلى routes/console.php في البنية الحديثة، أو داخل تعريف الجدولة المناسب لإصدار مشروعك.

use Illuminate\Support\Facades\Schedule;

Schedule::command('horizon:snapshot')->everyFiveMinutes();

يجب تشغيل مجدول Laravel نفسه كل دقيقة، مثل إدخال cron التالي. إضافة التعريف دون تشغيل المجدول لن تنتج لقطات تلقائية.

* * * * * cd /var/www/app && /usr/bin/php artisan schedule:run >> /dev/null 2>&1

11. قائمة التحقق واستكشاف الأعطال

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

  • تأكد أن QUEUE_CONNECTION يساوي redis، وليس sync.
  • طابق اسم الطابور المرسل مع قائمة الطوابير في Horizon.
  • راجع APP_ENV ووجود إعداد مشرف للبيئة الحالية.
  • بعد تغيير البيئة، أعد بناء كاش الإعدادات وأعد تشغيل Horizon بأمان.
  • افحص سجلات Laravel وSupervisor واتصال Redis من خادم العامل.
  • جهّز سياسة استمرارية وذاكرة Redis بما يمنع فقد الطوابير أو إخلاء مفاتيحها عشوائيًا.

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

SHARE شارك المقال
عبدالرحمن ربيع
كتبه

عبدالرحمن ربيع

Software Engineer & AI Builder

مطور برمجيات متكامل ومصمم جرافيك مع أكثر من 4 سنوات خبرة في بناء تطبيقات الويب الحديثة باستخدام PHP و JavaScript و HTML و CSS. خلفية قوية في تصميم UI/UX واستخدام متقدم لأدوات الذكاء الاصطناعي لتعزيز كفاءة التطوير والأتمتة واتخاذ القرارات. حاصل على ماجستير تنفي...

RELATED / READ NEXT

مقالات ذات صلة

كل المقالات
COMMENTS

التعليقات (0)

كن أول من يعلّق على هذا المقال.

أضف تعليقك

يظهر تعليقك بعد المراجعة.