تصميم نظام Notifications قابل للتوسع باستخدام Message Queues

تصميم نظام Notifications قابل للتوسع باستخدام Message Queues

في التطبيقات الحديثة، نظام الإشعارات لم يعد مجرد ميزة إضافية، بل جزء أساسي من تجربة المستخدم، سواء كنا نتحدث عن إشعارات Email، SMS، Push Notifications أو حتى Webhooks. التحدي الحقيقي يظهر عندما يتحول النظام من مئات الإشعارات يوميًا إلى ملايين الرسائل في الساعة. هنا نحتاج إلى Scalable Notification System مصمم بطريقة صحيحة من البداية.

في هذا المقال سنشرح خطوة بخطوة كيفية تصميم نظام إشعارات قابل للتوسع باستخدام Message Queues مثل RabbitMQ أو Kafka، وما هي المكونات الأساسية، وأنماط التصميم، وآليات التعامل مع الفشل، وتوزيع الأحمال.

لماذا نحتاج إلى Scalable Notification System؟

أي تطبيق نموذجي يبدأ بإرسال إشعارات من الكود مباشرة، مثل استدعاء خدمة إرسال Email أو SMS من داخل الـ API. هذا النموذج يعمل في البداية، لكنه يفشل عندما:

  • يزداد عدد المستخدمين بشكل كبير.
  • تتنوع أنواع الإشعارات: Email, SMS, Mobile Push, In-App.
  • تحتاج لتتبع حالة الإشعار: وصل؟ فشل؟ تمت إعادة المحاولة؟
  • تريد عزل منطق العمل عن مزودي الخدمات الخارجيين (Email/SMS providers).

التصميم القابل للتوسع يعتمد على فصل عملية إنتاج الإشعار عن عملية إرساله باستخدام Message Queue. بهذه الطريقة:

  • الـ API أو الخدمة الأساسية تصبح سريعة وخفيفة، لأنها لا تنتظر إرسال الإشعار.
  • يمكن زيادة عدد الـ Workers المسؤولة عن إرسال الإشعارات حسب الحمل.
  • يمكن تغيير مزود الخدمة (مثلاً من SendGrid إلى SES) بدون التأثير على بقية النظام.

إن لم تكن معتادًا على أنظمة الرسائل، يمكنك مراجعة مقالنا عن RabbitMQ مقابل Kafka: أي Message Queue تختار لمشروعك؟ لفهم الفروق بينهما قبل تصميم النظام.

المكونات الأساسية في Scalable Notification System

بغض النظر عن التقنية المستخدمة (RabbitMQ أو Kafka)، تقريبًا كل نظام إشعارات قابل للتوسع يحتوي على نفس المكونات المنطقية:

  1. Notification Producer (المنتِج): جزء من النظام يقوم بإنشاء طلب الإشعار (مثلاً عند تسجيل مستخدم جديد أو إجراء عملية مهمة).
  2. Message Queue / Broker: الوسيط الذي يستقبل الرسالة ويحفظها مؤقتًا ويضمن وصولها للـ Consumers.
  3. Notification Processor / Worker: خدمة أو عدة خدمات تقرأ الرسائل من الـ Queue وتنفذ عملية الإرسال.
  4. Notification Channels: Email, SMS, Push, Webhook ... إلخ.
  5. Notification Store / Logs: قاعدة بيانات أو نظام تخزين لسجل الإشعارات وحالتها.

لنأخذ مثالاً عملياً يوضح تدفق العمل.

تدفق الإشعار (Notification Flow)

  1. مستخدم جديد يسجل في الموقع.
  2. خدمة الـ API تستقبل الطلب وتقوم بحفظ المستخدم في قاعدة البيانات.
  3. بدلاً من استدعاء خدمة البريد مباشرة، تقوم بإرسال رسالة إلى الـ Message Queue تحتوي على:
    • نوع الإشعار: EMAIL_VERIFICATION
    • بيانات المستخدم: user_id, email
    • اللغة، القالب، البيانات الإضافية (Template Params)
  4. أحد الـ Workers الخاصة بالبريد الإلكتروني يقرأ الرسائل من الـ Queue، ويستخدم مزود خدمة Email لإرسال الرسالة.
  5. يقوم الـ Worker بتحديث حالة الإشعار في قاعدة البيانات: SENT أو FAILED.

نموذج شبيه تم تطبيقه في مقال كيفية بناء نظام Email Verification في Django وFastAPI، لكن ما سنناقشه هنا أوسع وأكثر عمومية وقابل للتوسع بدرجة أعلى.

اختيار Message Queue: RabbitMQ أم Kafka؟

في تصميم Scalable Notification System، اختيارك للـ Message Broker مهم لأنه يؤثر على:

  • سرعة المعالجة وعدد الرسائل في الثانية.
  • قابلية التوسع أفقياً.
  • درجة الضمان في التسليم (Delivery Guarantees).

RabbitMQ

  • مناسب لأنظمة الإشعارات الكلاسيكية حيث تحتاج:
    • Queue لكل نوع إشعار أو قناة.
    • آلية Routing عبر Exchange (مثلاً Fanout, Direct, Topic).
  • دعم جيد للـ Acknowledgements وإعادة المحاولة (Retry) وDead Letter Queues.
  • أسهل في التعامل في كثير من حالات الـ Microservices.

Kafka

  • مناسب للحالات ذات الحجم الضخم جداً (streaming) مع مئات الآلاف إلى الملايين من الرسائل في الثانية.
  • يوفر Log دائم للرسائل، يمكن إعادة قراءته (Replay).
  • ممتاز عند الحاجة لتحليلات قوية على بيانات الإشعارات، أو ربطها مع أنظمة Real-Time Analytics.

إذا كان حجم الإشعارات لديك كبير ولكن في إطار تطبيق Web أو Mobile عادي، غالبًا RabbitMQ سيكون كافيًا ومناسبًا. إذا كنت في بيئة Enterprise ضخمة أو لديك نظام Streaming كبير، فـ Kafka خيار قوي. يمكنك مراجعة المقارنة التفصيلية في Kafka مقابل Redis Pub/Sub مقابل RabbitMQ: مقارنة الأداء والمرونة.

تصميم بنية النظام (Architecture Design)

1. تعريف أنواع الإشعارات (Notification Types)

أول خطوة هي تعريف أنواع الإشعارات التي سيدعمها النظام:

  • Transactional Notifications: مثل رسائل تأكيد التسجيل، إعادة تعيين كلمة المرور، الفواتير.
  • Marketing Notifications: حملات تسويقية جماعية، عروض، تذكيرات.
  • System Alerts: تنبيهات داخلية، إشعارات إدارية، مراقبة أنظمة.

كل نوع إشعار يمكن أن يسير في مسار مختلف بالـ Queue، خصوصًا لو كان حجم التسويق كبيرًا وقد يؤثر على سرعة الرسائل الحساسة (مثل إعادة تعيين كلمة المرور).

2. تصميم نموذج البيانات (Data Model)

على الأقل تحتاج جدول (أو Collection) لتخزين الإشعارات:

  • notification_id
  • user_id (إن وجد)
  • channel: EMAIL, SMS, PUSH, IN_APP, WEBHOOK
  • type: VERIFY_EMAIL, RESET_PASSWORD, MARKETING_CAMPAIGN ... إلخ
  • status: PENDING, SENT, FAILED, RETRYING, CANCELLED
  • payload: JSON يحتوي على البيانات المطلوبة للإرسال (template data...)
  • error_message (عند الفشل)
  • created_at, updated_at, sent_at

ليس شرطًا أن يسجل النظام كل إشعار في قاعدة البيانات قبل الإرسال، لكنه مفيد جدًا:

  • لتتبع مشاكل التسليم.
  • لأغراض الدعم الفني.
  • للبناء فوقها لاحقًا (Reports, Analytics).

3. تصميم الـ Message Format داخل الـ Queue

الرسالة المرسلة للـ Queue يجب أن تكون عامة وقابلة للتطوير، مثلاً:

{
  "notification_id": "uuid",
  "user_id": 123,
  "channel": "EMAIL",
  "type": "VERIFY_EMAIL",
  "payload": {
    "email": "[email protected]",
    "name": "User Name",
    "verification_link": "https://example.com/verify?token=..."
  },
  "meta": {
    "locale": "ar",
    "priority": "high"
  }
}

في RabbitMQ يمكنك إرسال هذه الرسالة كـ JSON عبر Queue اسمها مثلاً notifications.email.

استخدام RabbitMQ في تصميم نظام الإشعارات

لنأخذ سيناريو عملي باستخدام RabbitMQ:

1. تصميم الـ Exchanges والـ Queues

  • Exchange رئيسي:
    • اسم: notifications.exchange
    • نوع: Topic أو Direct.
  • Queues متعددة حسب القناة:
    • notifications.email
    • notifications.sms
    • notifications.push
  • Routing Keys:
    • email.* لكل إشعارات الإيميل.
    • sms.* لكل SMS.
    • push.* لكل Push Notifications.

الـ Producer ينشر الرسالة في Exchange مع Routing Key مناسب؛ RabbitMQ يوجهها تلقائياً للـ Queue الصحيحة.

2. الـ Workers / Consumers

  • خدمة مستقلة لاستقبال رسائل notifications.email:
    • تقرأ الرسالة.
    • تستخدم Template Engine لإعداد محتوى البريد.
    • تتصل بمزود البريد (SMTP / API) لإرسال الرسالة.
    • تحدث حالة الإشعار في قاعدة البيانات.
    • ترسل ACK للـ Queue حتى يعتبر RabbitMQ الرسالة مستهلكة بنجاح.
  • يمكن تشغيل عدة نسخ من نفس الـ Worker خلف Load Balancer أو عبر Docker/Kubernetes لزيادة القدرة على المعالجة.

ربط هذا التصميم مع تصميم REST API قابل للتوسع يجعل النظام ككل قادرًا على العمل تحت أحمال عالية بشكل مستقر.

استخدام Kafka في تصميم نظام الإشعارات

في حالة Kafka، الفكرة مشابهة لكن المصطلحات تختلف:

  • بدل Queues لدينا Topics.
  • يمكنك إنشاء Topic واحد مثل notifications وتقسيمه إلى Partitions.
  • الـ Consumers يتم تنظيمهم ضمن Consumer Groups.

في Kafka يمكن أن يكون لديك:

  • notifications-email Topic لإشعارات الإيميل.
  • notifications-sms Topic لإشعارات SMS.

الميزة هنا أن Kafka يحتفظ بالرسائل لفترة، ويمكنك:

  • إعادة تشغيل الـ Consumer من Offset معين.
  • إضافة Consumer جديد بدون التأثير على الآخرين.
  • بناء أنظمة Analytics تقرأ من نفس الـ Topics لتحليل سلوك المستخدمين تجاه الإشعارات.

التعامل مع الفشل وإعادة المحاولة (Retries & DLQ)

أي Scalable Notification System يجب أن يتعامل مع حالات الفشل، مثل:

  • انقطاع خدمة مزود البريد أو الـ SMS.
  • تجاوز الـ Rate Limit.
  • خطأ في صيغة البريد أو الرقم.

1. سياسات إعادة المحاولة (Retry Policies)

يمكنك تطبيق واحدة أو أكثر من السياسات التالية:

  • Immediate Retry: إعادة المحاولة مباشرة لعدد محدود من المرات (مثلاً 3 محاولات).
  • Exponential Backoff: زيادة مدة الانتظار بين المحاولات تدريجيًا: 1 دقيقة، 2 دقيقة، 4 دقائق ... إلخ.
  • Scheduled Retry Queue: إرسال الرسائل الفاشلة لصف خاص يعيد معالجتها بعد وقت معين.

2. Dead Letter Queue (DLQ)

إذا فشل الإشعار بعد عدد معين من المحاولات، يتم إرساله إلى DLQ، Queue خاصة للإشعارات التي فشلت بشكل نهائي. يمكن بعدها:

  • إجراء تحليل يدوي للأخطاء.
  • إعادة إرسال جزء منها يدويًا بعد إصلاح المشكلة.
  • استخدامها كـ مصدر للـ Analytics عن أسباب الفشل الأكثر شيوعًا.

الـ Scalability: كيف نرفع قدرة النظام؟

نظام الإشعارات الجيد يجب أن يستجيب للنمو بدون إعادة بناء من الصفر. كيف نحقق ذلك؟

1. زيادة عدد الـ Workers أفقياً

طالما المعالجة منفصلة في Workers مستقلة، يمكنك:

  • تشغيل 2 Workers اليوم، و10 غدًا عند إطلاق حملة تسويقية.
  • توزيع الـ Workers على عدة خوادم أو حاويات Docker متعددة.

يمكنك الاستفادة من مبادئ التشغيل بالحاويات كما شرحنا في مقال تعلم الدوكر: شرح أساسيات التعامل مع الحاويات لتبسيط عملية تشغيل وتوسيع الـ Workers.

2. فصل الـ Channels في خدمات مستقلة

بدلاً من وضع Email و SMS و Push في خدمة واحدة، يمكن عمل:

  • Notification Email Service
  • Notification SMS Service
  • Notification Push Service

كل خدمة تقرأ من Queue أو Topic خاص بها. هذا:

  • يسمح لكل قناة بالتوسع بشكل مستقل.
  • يعزل المشاكل؛ فشل مزود Email لا يؤثر على SMS.

3. التحكم في الأولويات (Prioritization)

يمكن تخصيص Queues مختلفة للإشعارات ذات الأولوية العالية والمنخفضة:

  • notifications.email.high للرسائل الحساسة (Reset Password).
  • notifications.email.low للرسائل التسويقية.

يمكن تعيين عدد أكبر من الـ Workers على الـ Queue ذات الأولوية العالية، لضمان وصول الرسائل الحساسة بسرعة.

مراعاة الأداء (Performance) والتجربة الفعلية

لجعل Scalable Notification System فعّالاً في الواقع، يجب الانتباه إلى الآتي:

  • Batching: إرسال الإشعارات في مجموعات (Batches) عندما تدعم الـ API ذلك، لتقليل عدد الطلبات الخارجية.
  • Connection Pooling: إعادة استخدام الاتصالات مع مزودي الخدمات بدل فتح اتصال جديد لكل رسالة.
  • Caching: تخزين بعض البيانات المتكررة (مثل قوالب البريد) في Cache بدل قراءة قاعدة البيانات دائماً.
  • Monitoring: مراقبة:
    • عدد الرسائل في الـ Queue.
    • زمن معالجة الإشعار.
    • نسبة الفشل.

وجود مراقبة وتنبيهات (Observability) ضروري لمعرفة نقاط الاختناق قبل أن يشعر بها المستخدم.

اعتبارات أمنية (Security Considerations)

نظام الإشعارات يتعامل غالباً مع:

  • بيانات حساسة: بريد، أرقام هواتف، روابط خاصة (مثل Links لإعادة تعيين كلمة المرور).

لذلك يجب:

  • تشفير الاتصال مع الـ Message Broker (TLS).
  • تقييد الوصول للـ Queues / Topics للمكونات المصرح لها فقط.
  • إخفاء البيانات الحساسة في الـ Logs (Masking).
  • استخدام Tokens قصيرة العمر في الروابط الحساسة.

كيف تبدأ فعلياً في بناء Scalable Notification System؟

خطوات عملية مقترحة:

  1. حدد Types وقنوات الإشعارات التي تحتاجها (Email, SMS, Push...)
  2. اختر Message Broker مبدئيًا (RabbitMQ مناسب لمعظم الحالات).
  3. صمم Message Format عام (JSON) يشمل channel, type, payload.
  4. ابنِ Producer بسيط داخل الـ API:
    • عند حدث معين (مثل تسجيل مستخدم)، ينشر رسالة في الـ Queue بدل الإرسال المباشر.
  5. ابنِ Worker بسيط لـ Email:
    • يقرأ من Queue واحدة مثل notifications.email.
    • يتصل بمزود البريد ويرسل الرسالة.
    • يسجل النتيجة في قاعدة البيانات.
  6. أضف آلية Retries وDLQ لاحقًا أثناء توسعة النظام.
  7. قسّم الـ Workers حسب القنوات والـ Priority مع نمو النظام.

خلاصة

تصميم Scalable Notification System يعتمد على مبدأ أساسي: فصل إنتاج الإشعارات عن إرسالها باستخدام Message Queues مثل RabbitMQ أو Kafka. هذا الفصل يمنحك:

  • قابلية توسع أفقية عبر زيادة الـ Workers.
  • مرونة في تغيير مزودي الخدمات أو إضافة قنوات جديدة.
  • تحكم أفضل في الفشل وإعادة المحاولة.
  • إمكانية بناء طبقة Analytics وتقارير على بيانات الإشعارات.

ابدأ بتصميم بسيط ثم أضف التعقيد تدريجيًا مع نمو التطبيق. ومع تطبيق مبادئ هندسية جيدة في الـ API والـ Infrastructure، ستحصل على نظام إشعارات قادر على التعامل مع ملايين الرسائل بكفاءة وموثوقية عالية.

حول المحتوى:

شرح كيفية بناء نظام إشعارات قادر على التعامل مع ملايين الرسائل باستخدام RabbitMQ أو Kafka.

هل كان هذا مفيدًا لك؟

أضف تعليقك