تشغيل Laravel Reverb على سيرفرك: إشعارات لحظية بلا Pusher

Laravel Reverb خادم WebSocket رسمي يعمل داخل سيرفرك بدل Pusher، فلا تدفع لكل رسالة وتبقى بيانات الأحداث داخل المملكة. تشغّله خلف Nginx مع TLS كخدمة systemd، وسيرفر 2vCPU/4GB يحتمل آلاف الاتصالات المتزامنة إذا ضبطت حد الملفات المفتوحة.
Laravel Reverb هو خادم WebSocket الرسمي من فريق Laravel، تشغّله على سيرفرك أنت فيستقبل اتصالات المتصفحات ويبث لها الأحداث لحظياً بنفس بروتوكول Pusher، لذلك يعمل Laravel Echo معه بلا أي تعديل في كود الواجهة. الخطوات المختصرة: تثبّته بأمر php artisan install:broadcasting، تضبط متغيرات REVERB في ملف .env، تشغّله بأمر php artisan reverb:start على المنفذ 8080 داخلياً، ثم تضعه خلف Nginx الذي يتولى شهادة TLS ويمرر ترويسات Upgrade وConnection حتى يتحول طلب HTTP إلى اتصال WebSocket. بعدها تحوّله إلى خدمة systemd تعيد تشغيله تلقائياً، وترفع حد الملفات المفتوحة إلى 10,000 أو أكثر لأن كل اتصال يستهلك ملفاً مفتوحاً واحداً. السبب الذي يدفع الشركات لهذه الخطوة اثنان: الكلفة، لأن الخدمات المستضافة تحاسبك على عدد الاتصالات والرسائل فتتضخم الفاتورة مع نمو الاستخدام، والسيادة، لأن كل حدث تبثه (حالة طلب، إشعار موظف، رسالة دعم) يمر عبر سيرفرات خارج المملكة. مع Reverb على سيرفر في الرياض يبقى كل شيء داخل بنيتك، ويصل الحدث للمستخدم في نحو 8ms من الرياض بدل رحلة ذهاب وعودة لقارة أخرى.
لماذا تستبدل Pusher بـReverb ذاتي
Pusher وأمثاله خدمات ممتازة للبداية، لكن نموذج التسعير مبني على سقفين: عدد الاتصالات المتزامنة وعدد الرسائل اليومية. لوحة تحكم داخلية فيها 300 موظف متصل طوال اليوم، وكل موظف يستقبل 200 حدث يومياً، تعني 60,000 رسالة في اليوم الواحد، وهذا الرقم يتضاعف بسرعة إذا أضفت إشعارات الحالة أو مؤشرات الكتابة في المحادثات. مع Reverb تتحول الكلفة إلى موارد سيرفر تملكها، ولا يوجد عدّاد للرسائل.
النقطة الثانية أهم للشركات التي تتعامل مع جهات حكومية أو بيانات حساسة، فمحتوى الحدث نفسه يمر عبر الخدمة الخارجية، وإذا كنت تبث رقم هوية أو حالة معاملة أو اسم عميل فأنت تنقل بيانات شخصية خارج الحدود. تشغيل Reverb على خادم سحابي سعودي يحل هذه المسألة من جذرها، لأن الاتصال يبدأ من المتصفح وينتهي في سيرفرك داخل المملكة.
| المعيار | خدمة مستضافة خارجية | Reverb على سيرفرك |
|---|---|---|
| الكلفة | تتصاعد مع الاتصالات والرسائل | ثابتة ضمن موارد السيرفر |
| موقع البيانات | خارج المملكة غالباً | الرياض |
| زمن الوصول | من 60 إلى 150ms حسب المنطقة | نحو 8ms من الرياض |
| الصيانة | على المزوّد | عليك (خدمة واحدة) |
التثبيت وضبط متغيرات البيئة
في Laravel 11 و12 يكفي أمر واحد يثبّت Reverb ويضيف ملف routes/channels.php وإعدادات Echo في الواجهة:
php artisan install:broadcasting
# يسألك عن Reverb ويثبّت laravel-echo و pusher-js
npm run build
بعدها تضبط ملف .env، والفكرة هنا أن هناك عنوانين مختلفين: عنوان داخلي يستمع عليه Reverb نفسه، وعنوان عام يتصل به المتصفح عبر Nginx:
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=482113
REVERB_APP_KEY=k7f2x9q1
REVERB_APP_SECRET=s3cr3t-change-me
# العنوان العام الذي يراه المتصفح
REVERB_HOST=ws.example.sa
REVERB_PORT=443
REVERB_SCHEME=https
# العنوان الداخلي الذي يستمع عليه Reverb
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080
جعل REVERB_SERVER_HOST يساوي 127.0.0.1 يعني أن المنفذ 8080 لا يُفتح للعالم، وكل الحركة تمر عبر Nginx وحده، فيقل سطح الهجوم ويبقى TLS في مكان واحد. لا تنس أن تعيد بناء الواجهة بعد أي تغيير في متغيرات VITE_REVERB_* لأنها تُحقن وقت البناء.
Nginx مع TLS وترويسات الترقية
أكثر خطأ نراه في هذا الإعداد هو نسيان ترويسات الترقية، فيصل الطلب لـReverb كطلب HTTP عادي ويرفضه، وتظهر في المتصفح رسالة فشل الاتصال بعد 3 محاولات. هذا الإعداد يعمل كما هو:
server {
listen 443 ssl http2;
server_name ws.example.sa;
ssl_certificate /etc/letsencrypt/live/ws.example.sa/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ws.example.sa/privkey.pem;
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_read_timeout 120s;
proxy_pass http://127.0.0.1:8080;
}
}
قيمة proxy_read_timeout مهمة، لأن Nginx يغلق الاتصال الخامل بعد 60 ثانية افتراضياً، بينما يرسل Echo نبضة ping كل 30 ثانية تقريباً، فرفعها إلى 120 ثانية يمنع الانقطاعات العشوائية. ارفع كذلك worker_connections في nginx.conf إلى 10,000 على الأقل، لأن كل اتصال WebSocket يستهلك اتصالين داخل Nginx: واحد مع المتصفح وواحد مع Reverb.
تشغيله كخدمة systemd
تشغيل reverb:start من نافذة SSH يعني أنه يموت مع إغلاقها، والحل خدمة systemd تعيد تشغيله عند أي سقوط وترفع حد الملفات المفتوحة:
[Unit]
Description=Laravel Reverb
After=network.target redis-server.service
[Service]
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php artisan reverb:start --no-interaction
Restart=always
RestartSec=3
LimitNOFILE=20000
[Install]
WantedBy=multi-user.target
تحفظ الملف في /etc/systemd/system/reverb.service ثم تنفذ systemctl daemon-reload وsystemctl enable --now reverb. إن كنت تستخدم supervisor أصلاً لعمّال الطوابير فلا مانع من إضافته هناك مع minfds=20000، المهم أن يكون هناك مشرف واحد مسؤول عن إعادة التشغيل. بعد كل نشر جديد نفذ php artisan reverb:restart حتى يقرأ Reverb الكود الجديد، لأنه عملية طويلة العمر تحمل الكود في الذاكرة مثل عمّال الطوابير تماماً.
تفصيلة أخرى تفرق في الأحمال الكبيرة: Reverb يعتمد على حلقة أحداث ReactPHP، والحلقة الافتراضية تستخدم stream_select المحدودة بـ1,024 اتصالاً. إذا توقعت أكثر من ذلك ثبّت امتداد ext-uv عبر PECL، وسيستخدمه Reverb تلقائياً بلا أي تعديل في الكود.
كم اتصالاً يحتمل سيرفر نمو 2vCPU/4GB
الاتصال الخامل في Reverb رخيص جداً، يستهلك تقريباً من 20 إلى 50 كيلوبايت من الذاكرة حسب عدد القنوات المشترك فيها. على سيرفر بباقة نمو (2 vCPU و4GB RAM) يشغّل التطبيق نفسه وقاعدة البيانات وRedis، يبقى لـReverb عادة نحو 1.5GB، وهذا يكفي نظرياً لعشرات الآلاف من الاتصالات الخاملة. الحد الحقيقي ليس الذاكرة بل المعالج وقت البث:
- بث حدث واحد لقناة فيها 5,000 مشترك يعني 5,000 عملية كتابة على الشبكة، وReverb عملية واحدة تعمل على نواة واحدة.
- في اختباراتنا التقريبية، نواة واحدة تبث بارتياح ما بين 15,000 و25,000 رسالة في الثانية لرسائل صغيرة أقل من 1KB.
- الرقم العملي الآمن لسيرفر نمو هو من 3,000 إلى 5,000 اتصال متزامن مع حركة بث معتدلة، مع هامش للتطبيق وقاعدة البيانات.
إذا تجاوزت هذا فأمامك خياران: ترقية الموارد بإضافة vCPU بـ500 ريال سنوياً لكل نواة، أو تشغيل أكثر من نسخة Reverb كما في القسم التالي. باقة نمو نفسها بـ6,900 ريال سنوياً شامل الضريبة، أي نحو 19 ريالاً في اليوم، وتفاصيل كل الباقات في صفحة الأسعار. قِس دائماً بأداة مثل k6 أو artillery قبل الإطلاق بدل الاعتماد على التقدير.
التوسعة عبر Redis pub/sub حين تتعدد النسخ
نسخة Reverb الواحدة لا تعرف شيئاً عن الاتصالات الموجودة في نسخة أخرى، فلو شغّلت نسختين خلف موزع أحمال وبث التطبيق حدثاً وصل للنسخة الأولى، لن يراه المشتركون في النسخة الثانية. الحل مدمج في Reverb نفسه:
REVERB_SCALING_ENABLED=true
REDIS_HOST=10.0.0.5
REDIS_PORT=6379
مع هذا الخيار تنشر كل نسخة أي رسالة تستقبلها إلى Redis، وتستمع كل النسخ لنفس القناة، فيصل الحدث لكل المشتركين أينما كان اتصالهم. يكفي أن تشير كل النسخ لنفس خادم Redis، وأن يوزع Nginx الاتصالات بينها عبر upstream بخوارزمية least_conn لأن الاتصالات طويلة العمر. على سيرفر واحد بـ4 أنوية تستطيع تشغيل 3 نسخ على المنافذ 8080 و8081 و8082 وترك نواة للتطبيق، فتستغل كل الأنوية بدل نواة واحدة.
المراقبة وما يجب أن تتابعه
Reverb يدعم Laravel Pulse مباشرة، فيعرض عدد الاتصالات ومعدل الرسائل في لوحة واحدة. تابع ثلاثة أرقام: الاتصالات المتزامنة، والرسائل في الثانية، واستهلاك المعالج لعملية Reverb، فإذا تجاوز المعالج 70% بشكل مستمر فهذا وقت إضافة نسخة. راقب كذلك سجل systemd عبر journalctl -u reverb -f بحثاً عن رسائل Too many open files، فهي تعني أن حد LimitNOFILE صار أقل من حاجتك.
خطوات التنفيذ من اليوم
- ثبّت Reverb بأمر
install:broadcastingواضبط العنوانين الداخلي والعام في.env. - أضف نطاقاً فرعياً مثل
ws.بشهادة TLS وإعداد Nginx أعلاه مع ترويسات الترقية. - حوّله إلى خدمة systemd مع
LimitNOFILE=20000، وأضفreverb:restartلسكربت النشر. - ثبّت
ext-uvإذا توقعت أكثر من 1,000 اتصال. - اختبر الحمل بـk6 حتى تعرف سقف سيرفرك الفعلي، ثم فعّل
REVERB_SCALING_ENABLEDعند الحاجة لنسخ متعددة.
إن كانت تطبيقاتك تحتاج سيرفراً سعودياً يحمل Reverb مع Laravel وقاعدة البيانات في مكان واحد، فخادم كلاود السحابي يتفعّل خلال ثوانٍ بعد الدفع، تبدأ بباقة نمو ثم تضيف الأنوية حسب ما تقوله أرقام المراقبة لا التخمين.


