Get a quote

بناء لوحات تحكم آنية للعمليات باستخدام WebSocket في Go وRedis Pub/Sub

كل مشغّل مطعم أو نظام SaaS يريد لوحة تحكم حية. قراراتك في طبقة WebSocket تحدد ما إذا كانت هذه اللوحة تتحمل الحمل أم تنهار عند أول خمسين مستخدماً متزامناً.

كل مشغّل مطعم أو منصة SaaS يريد في النهاية لوحة تحكم حية: الطلبات الواردة، الطاولات المشغولة، حركة المخزون، حالة قوائم الانتظار. القرارات التي تتخذها في طبقة WebSocket تحدد ما إذا كانت هذه اللوحة تعمل بسلاسة أم تنهار عند أول خمسين مستخدماً متزامناً.

لماذا يفشل HTTP Polling في لوحات التشغيل؟

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

خذ خمسين مستخدماً متزامناً يستطلعون مرة في الثانية. هذا خمسون طلب HTTP في الثانية، خمسون استعلام قاعدة بيانات في الثانية على نفس الجداول. والأسوأ أن معظم هذه الاستجابات متطابقة لأنه لم يتغير شيء. الخادم يعيد قراءة نفس البيانات وتسلسلها مراراً دون أي قيمة مضافة.

WebSockets تحل المشكلتين: اتصال مستمر واحد لكل عميل، والخادم يدفع الأحداث فور وقوعها دون استعلامات غير ضرورية.

البنية الأساسية: Hub مركزي مع Redis Pub/Sub

نمط Hub هو القلب: كائن مركزي يدير جميع الاتصالات النشطة ويوزع الرسائل عليها. كل مستأجر (tenant) يحصل على خريطة اتصالاته الخاصة:

type Hub struct {
    connections map[string]map[*Connection]bool
    register    chan *Connection
    unregister  chan *Connection
    broadcast   chan Message
    mu          sync.RWMutex
}

تدفق الرسائل: حدث تطبيقي (طلب جديد، دفع مكتمل) يُنشر على قناة Redis، goroutine المشترك في Redis يستقبله، يُدفع إلى قناة broadcast في Hub، وHub يوصّله لجميع الاتصالات المفتوحة لذلك المستأجر.

عزل المستأجرين ليس اختيارياً. الخريطة المفهرسة بـ tenantID تجعله مستحيل الكسر هيكلياً في الكود، لا مجرد اتفاقية يمكن كسرها.

التوسع عبر نسخ Go متعددة

نسخة Go واحدة كافية حتى تحتاج نسخاً متعددة للتوافرية أو التحميل. المشكلة: عميل WebSocket متصل بالنسخة A، حدث يُعالَج على النسخة B. النسخة B تحتاج إعلام العميل على النسخة A.

Redis Pub/Sub هو الحل. كل نسخة تشترك في نفس قنوات Redis. عند نشر أي حدث، جميع النسخ تستقبله وتوصّله لاتصالاتها المحلية لذلك المستأجر.

التعامل مع إعادة الاتصال

اتصالات WebSocket تنقطع: متصفحات الجوال تقطع عند نوم الهاتف، Load Balancers تُغلق الاتصالات الخاملة، الشبكات اللبنانية وشبكات المنطقة العربية يمكن أن تكون غير مستقرة أحياناً.

الحل من طرف العميل: إعادة محاولة أسية تبدأ من 500ms وتضاعف مع كل فشل حتى 30 ثانية.

من طرف الخادم: احتفظ بذاكرة تخزين قصيرة للأحداث الأخيرة مُفهرسة برقم تسلسلي. عند إعادة الاتصال يطلب العميل الأحداث منذ آخر تسلسل تلقاه. للوحات الحالة المجمّعة يمكنها إرسال لقطة كاملة عند أول رسالة بعد إعادة الاتصال.

المصادقة على اتصالات WebSocket

طلب HTTP Upgrade يحمل نفس cookies والـ headers للطلبات العادية. تحقق من JWT قبل ترقية الاتصال. إذا فشل التحقق أعد 401 ولا تُنشئ الاتصال.

مشكلة تجديد التوكن أصعب: JWT صادر عند فتح الاتصال قد ينتهي صلاحيته والاتصال لا يزال مفتوحاً. أبسط حل: أغلق الاتصال عند انتهاء الصلاحية وأجبر العميل على إعادة الاتصال مع توكن جديد.

اعتبارات الإنتاج

كل goroutine تستهلك 2 إلى 8 كيلوبايت في حالتها الأولية. ألف اتصال متزامن تعني ألفي goroutine زائد ذاكرة قنوات الإرسال. خطط لـ 0.5 إلى 1 ميجابايت لكل عميل متصل عند تحديد حجم النسخ.

تسريبات الذاكرة تأتي من اتصالات لا تُغلق بشكل نظيف. طبّق آلية ping/pong دورية. إذا لم يستجب العميل لـ ping خلال 10 ثوانٍ أغلق الاتصال من الخادم.

الإيقاف المتناسق ضروري: عند SIGTERM أوقف قبول اتصالات جديدة، أغلق الاشتراك في Redis، ثم أغلق جميع الاتصالات المتبقية بإطار CloseMessage مع إعطاء العملاء وقتاً للاتصال بنسخة أخرى.

دروس مستخلصة من الإنتاج

ابدأ بنسخة واحدة قبل إضافة Redis. تحقق من عمل البنية الأساسية أولاً ثم أضف الطبقة الموزعة.

المستهلكون البطيئون سيعطلون Hub إذا لم تتعامل معهم صراحةً. حالة default في broadcast select التي تقطع المستهلكين البطيئين ليست اختيارية، في ظل الحمل يمكن لعدد قليل من العملاء ببفر ممتلئ تسبب ضغطاً خلفياً يتدفق على جميع العملاء.

إذا لم تحتج رسائل من العميل للخادم، فكّر في Server-Sent Events بدلاً من WebSockets. أبسط في التنفيذ ويعمل بشكل أفضل عبر بعض الوكلاء.

هل تحتاج مساعدة؟

Voxire تبني أنظمة تشغيلية آنية لمنصات SaaS ومجموعات المطاعم في لبنان ومنطقة MENA. إذا كنت تصمم لوحة تحكم حية أو نظام مراقبة عمليات، نساعدك في بناء البنية الصحيحة من البداية.

https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp