عندما تنتقل المهام الثقيلة مثل إرسال البريد، معالجة الصور، توليد التقارير ومزامنة البيانات إلى الخلفية، تصبح الطوابير جزءًا حرجًا من بنية التطبيق. لكن تشغيل Queue Workers وحده لا يكفي؛ تحتاج إلى معرفة معدل المعالجة، وزمن التنفيذ، والمهام الفاشلة، وكيفية توزيع العمال على الطوابير. يقدم Laravel Horizon لوحة رسمية وإعدادًا برمجيًا موحدًا لإدارة طوابير Laravel المدعومة بواسطة Redis.
Horizon ليس نظام Queue مستقلًا؛ بل طبقة إدارة ومراقبة فوق Laravel Redis Queues، تنظم Workers وSupervisors وتعرض مؤشرات التشغيل والفشل في لوحة واحدة.
يعتمد هذا المقال على توثيق Laravel Horizon الرسمي لإصدار Laravel 13.x. راجع نسخة التوثيق المطابقة لإصدار مشروعك، خصوصًا قبل ترقية إصدار رئيسي من Horizon.
ما هو Laravel Horizon؟
Laravel Horizon حزمة رسمية توفر لوحة لمراقبة Redis Queues وإعدادًا قائمًا على الكود لعمال الطوابير. تعرض اللوحة معلومات مثل Job throughput وRuntime والمهام الحديثة والفاشلة، وتتيح البحث بالوسوم. أما ملف config/horizon.php فيجمع إعدادات العمال لكل بيئة، ما يجعلها قابلة للمراجعة والتتبع في Git.
يساعد Horizon على الإجابة عن أسئلة تشغيلية مهمة:
- هل معدل وصول Jobs أعلى من معدل معالجتها؟
- أي الطوابير تحتاج إلى Workers إضافية؟
- ما المهام الأكثر بطئًا أو فشلًا؟
- كيف نخصص موارد منفصلة لمهام الصور والتقارير والإشعارات؟
- كيف نعيد تشغيل العمال بعد النشر من دون قطع Job قيد التنفيذ؟
Horizon مقابل Queue Worker التقليدي
| الجانب | queue:work | Horizon |
|---|---|---|
| الـBackend | يدعم اتصالات Queue المختلفة. | يتطلب Redis Queue. |
| الإدارة | خيارات CLI ومدير عمليات. | Supervisors وإعداد موحد حسب البيئة. |
| الموازنة | توزيع يدوي غالبًا. | auto أو simple أو أولوية مرتبة. |
| الواجهة | لا توجد لوحة افتراضية. | لوحة للمهام والمقاييس والفشل. |
| الوسوم | ليست تجربة مراقبة مدمجة. | وسوم تلقائية ومخصصة للبحث. |
المتطلبات والقيود
- يجب أن يكون Queue connection المستخدم بواسطة Horizon من نوع Redis.
- توضح وثائق Laravel 13.x أن Horizon غير متوافق حاليًا مع Redis Cluster.
- يجب فهم أساسيات Jobs وQueues والمحاولات والفشل قبل ضبط Horizon.
- تحتاج بيئة الإنتاج إلى Process Monitor يبقي عملية Horizon قيد التشغيل.
QUEUE_CONNECTION=redisيستخدم Horizon اتصال Redis داخليًا اسمه horizon. الاسم محجوز، فلا تستخدمه اسمًا لاتصال آخر في config/database.php ولا قيمة لخيار use داخل إعداد Horizon.
تثبيت Laravel Horizon وإعداده
تثبيت الحزمة
composer require laravel/horizonنشر ملفات Horizon
php artisan horizon:installينشر الأمر ملف config/horizon.php وموفر الخدمة والأصول اللازمة.
تشغيل Horizon
php artisan horizonثم افتح:
https://example.com/horizonإعداد بيئات التشغيل
'environments' => [
'production' => [
'supervisor-default' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-default' => [
'maxProcesses' => 3,
],
],
],يختار Horizon الإعداد المطابق عادة لقيمة APP_ENV. تأكد من وجود بيئة لكل قيمة تستخدمها، ويمكن تعريف * كإعداد احتياطي إذا لم يوجد تطابق مباشر.
فهم Supervisors وWorkers والطوابير
Supervisor داخل Horizon هو مجموعة إعدادات تشرف على عدد من Worker processes وتحدد اتصال Redis والطوابير واستراتيجية الموازنة وحدود الموارد والمحاولات. يمكن للبيئة الواحدة أن تضم عدة Supervisors.
'defaults' => [
'supervisor-default' => [
'connection' => 'redis',
'queue' => ['default', 'notifications'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 60,
],
],متى تستخدم عدة Supervisors؟
- عندما تحتاج طابورًا عالي الأولوية بموارد محجوزة.
- عندما تستهلك Jobs معينة CPU أو ذاكرة كبيرة وتريد تحديد عددها.
- عندما تختلف قيمة timeout أو tries بين مجموعات المهام.
- عندما تريد Scaling مستقلًا لكل طابور.
وضع الصيانة
لا يعالج Horizon المهام أثناء Maintenance Mode افتراضيًا. لتجاوز ذلك لمسؤول محدد:
'force' => true,فعّل الخيار فقط إذا كانت المهام آمنة أثناء نشر التطبيق أو صيانته.
المحاولات والمهلة وBackoff وإدارة الذاكرة
عدد المحاولات tries
'tries' => 3,إذا لم تضبط tries يستخدم Horizon محاولة واحدة افتراضيًا، إلا إذا عرّفت Job خاصية $tries التي لها الأولوية. القيمة 0 تعني محاولات غير محدودة؛ استخدم معها $maxExceptions لمنع حلقة فشل لا تنتهي.
Middleware مثل WithoutOverlapping وRateLimited قد تستهلك Attempts من دون تنفيذ منطق Job كاملًا، لذلك لا تضبط tries بصورة عشوائية.
مهلة التنفيذ timeout
'timeout' => 60,اجعل Horizon timeout أكبر من أي Job-level timeout، وأقصر بعدة ثوانٍ من
retry_afterفيconfig/queue.php. وإلا قد تُقتل Job أثناء التنفيذ أو تُعالج مرتين.
Backoff وإعادة المحاولة
'backoff' => [1, 5, 10],ينتظر Horizon ثانية قبل الإعادة الأولى، ثم 5 ثوانٍ، ثم 10 ثوانٍ، ويستمر باستخدام آخر قيمة للمحاولات الإضافية. يقلل Exponential backoff الضغط على خدمة خارجية متعثرة.
إعادة تدوير Workers
'memory' => 128,
'maxJobs' => 1000,
'maxTime' => 3600,
'sleep' => 3,
'rest' => 0,
'nice' => 0,memory: الحد الأقصى بالميغابايت قبل إعادة Worker.maxJobs: عدد Jobs قبل إعادة التشغيل؛ الصفر يعطل الحد.maxTime: مدة حياة Worker بالثواني؛ الصفر يعطل الحد.sleep: الانتظار عندما لا توجد مهمة.rest: توقف بين كل مهمتين.nice: أولوية جدولة العملية في النظام.
استراتيجيات موازنة الطوابير
يدعم Horizon ثلاث قيم لخيار balance: auto وsimple وfalse.
Auto Balancing
يوزع Horizon العمليات ديناميكيًا وفق حمل كل Queue. يمكن الاستناد إلى الزمن المتوقع لتفريغ الطابور باستخدام time أو إلى عدد Jobs باستخدام size.
'connection' => 'redis',
'queue' => ['default', 'notifications'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,يسمح المثال بإضافة أو إزالة عملية واحدة كل ثلاث ثوانٍ. اضبط سرعة التغيير بعناية حتى لا تتأرجح العمليات بسرعة أو تتأخر في امتصاص موجة كبيرة.
ترتيب الطوابير في استراتيجية auto لا يفرض أولوية صارمة. إذا احتجت أولوية أو موارد مضمونة، افصل الطوابير في Supervisors مستقلة وحدد لكل منها minProcesses وmaxProcesses.
Simple Balancing
'queue' => ['default', 'notifications'],
'balance' => 'simple',
'processes' => 10,يقسم Horizon عدد العمليات الثابت بالتساوي؛ في المثال تحصل كل Queue على خمس عمليات.
No Balancing
'queue' => ['high', 'default'],
'balance' => false,
'minProcesses' => 1,
'maxProcesses' => 10,يعالج Horizon الطوابير حسب ترتيبها. لن ينتقل إلى default قبل إنهاء ما ينتظر في high، مع قدرته على تغيير إجمالي عدد Workers ضمن الحدود.
عزل المهام كثيفة الموارد
'production' => [
'supervisor-default' => [
'queue' => ['default'],
'minProcesses' => 1,
'maxProcesses' => 10,
],
'supervisor-images' => [
'queue' => ['images'],
'minProcesses' => 1,
'maxProcesses' => 2,
'memory' => 512,
'timeout' => 300,
],
],تشغيل Horizon وإدارته
| الأمر | وظيفته |
|---|---|
php artisan horizon | تشغيل العملية وجميع Supervisors للبيئة. |
php artisan horizon:status | عرض حالة Horizon. |
php artisan horizon:pause | إيقاف المعالجة مؤقتًا. |
php artisan horizon:continue | استئناف المعالجة. |
php artisan horizon:terminate | إنهاء سلس بعد إكمال المهام الحالية. |
php artisan horizon:pause-supervisor supervisor-1 | إيقاف Supervisor محدد. |
php artisan horizon:continue-supervisor supervisor-1 | استئناف Supervisor محدد. |
php artisan horizon:supervisor-status supervisor-1 | التحقق من حالة Supervisor. |
إعادة التحميل في التطوير
يدعم Horizon أمر horizon:listen بعد تثبيت Chokidar ووجود Node:
npm install --save-dev chokidar
php artisan horizon:listenداخل Docker أو Vagrant يمكن استخدام:
php artisan horizon:listen --pollنشر Horizon باستخدام Supervisor
شغل Horizon في الإنتاج تحت مدير عمليات يعيد تشغيله إذا توقف. مثال Supervisor على Ubuntu:
[program:horizon]
process_name=%(program_name)s
command=php /home/forge/example.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/horizon.log
stopwaitsecs=3600يجب أن تكون stopwaitsecs أطول من أطول Job؛ وإلا قد يقتل Supervisor المهمة قبل اكتمالها.
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start horizonضمن عملية النشر
php artisan horizon:terminateيكمل Horizon المهام الحالية ثم يتوقف، ويعيد مدير العمليات تشغيله بالكود الجديد. لا تكتفِ بنشر الملفات؛ فالعمال عمليات طويلة العمر لا تقرأ الكود الجديد تلقائيًا.
حماية لوحة Horizon في الإنتاج
تتوفر اللوحة في /horizon وتكون متاحة افتراضيًا في البيئة المحلية فقط. عدّل Gate viewHorizon داخل HorizonServiceProvider:
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user) {
return $user->is_admin === true;
});
}- اضبط
APP_ENV=productionوAPP_DEBUG=false. - استخدم Role أو Permission مخصصًا للمراقبة.
- أضف VPN أو Zero Trust أو IP allowlist عند الإمكان.
- لا تجعل حماية الشبكة بديلًا عن Gate إلا ضمن تصميم مصادقة مدروس.
- أضف CSP nonce عبر
Horizon::cspNonce()إذا كانت سياسة CSP تتطلب ذلك.
Metrics والإشعارات
تعرض لوحة Horizon Throughput وRuntime ومقاييس Jobs وQueues. حتى تظهر الرسوم التاريخية، جدول أمر Snapshot:
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();تأكد من تشغيل Laravel Scheduler على الخادم.
إشعارات وقت الانتظار الطويل
يستطيع Horizon إرسال إشعار عندما يتجاوز انتظار Queue حدًا معينًا، عبر قنوات مدعومة مثل البريد وSlack وSMS وفق إعداد التطبيق. اضبط حدود الانتظار لكل Connection وQueue في config/horizon.php، ثم سجل قنوات الإشعار في موفر Horizon بحسب التوثيق الرسمي.
المهام الفاشلة وتنظيف الطوابير
افحص Exception وPayload وعدد المحاولات قبل إعادة Job. إعادة كل المهام بلا فهم السبب قد تكرر آثارًا جانبية مثل إرسال بريد مرتين أو خصم مالي متكرر. صمم Jobs المهمة لتكون Idempotent قدر الإمكان.
php artisan horizon:forget 5
php artisan horizon:forget --allلحذف Failed Job من Redis عند استخدام Horizon، استخدم horizon:forget بدل queue:forget.
تنظيف Queue
php artisan horizon:clear
php artisan horizon:clear --queue=emailsأوامر المسح مدمرة للمهام المنتظرة. حدد الاتصال والطابور بدقة، وافحص أثر فقدان Jobs قبل التنفيذ في الإنتاج.
سيناريوهات عملية
تراكم آلاف الإشعارات
- راجع Wait time وThroughput لطابور notifications.
- تأكد من أن Horizon يعمل وأن Workers لا تنهار.
- افصل notifications في Supervisor مستقل.
- ارفع maxProcesses ضمن قدرة Redis والخادم ومزود البريد.
- استخدم Rate Limiting إذا كان المزود الخارجي يفرض حدودًا.
Job معالجة صور تستهلك CPU
- انقلها إلى Queue اسمها images.
- خصص Supervisor بحد أقصى صغير للعمليات.
- ارفع memory وtimeout بما يناسب الملف الأقصى.
- أعد تدوير Workers عبر maxJobs أو maxTime.
- راقب زمن التنفيذ والذاكرة قبل زيادة التوازي.
Job تُعالج مرتين
- تحقق من أن timeout أقصر من retry_after.
- راجع مدة Job الفعلية والاتصالات الخارجية.
- اجعل العملية Idempotent باستخدام مفتاح عمل فريد أو قفل مناسب.
- راجع WithoutOverlapping وunique jobs والمحاولات.
تغييرات الكود لا تظهر
- تذكر أن Horizon عملية طويلة العمر.
- نفذ
php artisan horizon:terminateأثناء النشر. - تأكد من أن Supervisor أعاد تشغيل العملية.
- راجع Working directory ومستخدم النظام ومسار PHP.
المشكلات الشائعة وحلولها
لوحة Horizon تعمل لكن Jobs لا تُعالج
- تأكد من
QUEUE_CONNECTION=redis. - راجع أسماء Connection وQueue في Job وSupervisor.
- تحقق من Maintenance Mode وخيار force.
- نفذ
php artisan horizon:status. - راجع Redis والLogs وSupervisor.
Horizon لا يستخدم إعداد البيئة
- طابق
APP_ENVمع مفتاح environments. - أضف إعداد
*احتياطيًا عند الحاجة. - امسح وأعد بناء Config cache.
- أنه Horizon وأعد تشغيله بعد تغيير الإعداد.
Jobs تفشل بسبب timeout
- قارن Horizon timeout وJob timeout وretry_after.
- لا ترفع الحدود قبل تحليل سبب البطء.
- قسم Job الكبيرة أو حسّن Query والخدمة الخارجية.
- اضبط stopwaitsecs في Supervisor بما يتجاوز أطول Job.
استهلاك الذاكرة يزداد
- اضبط memory وmaxJobs وmaxTime لإعادة تدوير Workers.
- حرر موارد كبيرة داخل Job ولا تحتفظ بحالة ساكنة.
- استخدم chunking وstreaming عند معالجة بيانات ضخمة.
- اعزل Jobs الثقيلة في Supervisor مستقل.
الأسئلة الشائعة
هل Horizon يعمل مع Database Queue؟
لا. يتطلب Horizon Redis لتشغيل الطوابير التي يديرها.
هل Horizon بديل عن Supervisor؟
لا. Horizon يدير Laravel Workers داخليًا، بينما Supervisor أو systemd يراقب عملية Horizon نفسها ويعيد تشغيلها إذا توقفت أو بعد النشر.
هل Auto Balancing يعني أولوية حسب ترتيب Queue؟
لا. يوزع Auto العمال حسب الحمل والاستراتيجية. استخدم Supervisors مستقلة للموارد المضمونة، أو balance=false إذا احتجت ترتيب معالجة صارمًا.
لماذا يجب إنهاء Horizon عند النشر؟
لأن Workers طويلة العمر تحتفظ بالكود المحمل. الإنهاء السلس يسمح لمدير العمليات بتشغيل نسخة جديدة.
هل يمكن تشغيل عدة Horizon instances؟
نعم ضمن بنية Redis مناسبة وإعدادات موارد مدروسة. استخدم Prefix وبيئات واتصالات صحيحة، وراقب مجموع Workers عبر الخوادم حتى لا تتجاوز قدرة قواعد البيانات والخدمات الخارجية.
خلاصة أفضل الممارسات
- افصل الطوابير حسب الأولوية وكلفة الموارد.
- اجعل timeout أقصر من retry_after وأطول من Job timeout.
- استخدم Backoff عند التعامل مع خدمات خارجية.
- صمم Jobs الحساسة لتكون Idempotent.
- أعد تدوير Workers لحماية العمليات طويلة العمر من تسرب الذاكرة.
- استخدم Supervisors مستقلة عندما تحتاج موارد مضمونة.
- شغل Horizon تحت Process Monitor.
- نفذ horizon:terminate ضمن كل نشر.
- احمِ لوحة Horizon بـGate وقيود شبكة.
- جدول horizon:snapshot واضبط تنبيهات الانتظار.
التعليقات (0)
لا توجد تعليقات بعد — كن أول من يشارك رأيه.
أضف تعليقك