كيفية تخزين الإشعارات وإعادة إرسالها عند اتصال المستخدم باستخدام RabbitMQ

كيفية تخزين الإشعارات وإعادة إرسالها عند اتصال المستخدم باستخدام RabbitMQ (offline notifications rabbitmq)

في أنظمة الإشعارات الحديثة، من الطبيعي أن يكون بعض المستخدمين غير متصلين (Offline) أثناء إرسال الإشعارات. هنا يأتي دور offline notifications rabbitmq، أي كيفية تخزين الإشعارات للمستخدمين غير المتصلين وإعادة إرسالها تلقائيًا عند عودتهم.

في هذا المقال من افهم صح – ifhmsah سنشرح بالتفصيل كيف تصمم نظام إشعارات يتعامل مع المستخدمين غير المتصلين باستخدام RabbitMQ، مع التركيز على:

  • آلية التعامل مع حالة المستخدم (متصل / غير متصل)
  • تخزين الإشعارات للمستخدم Offline
  • إعادة إرسال الإشعارات عند عودة الاتصال
  • نصائح تصميمية لزيادة الاعتمادية وقابلية التوسع

لماذا نحتاج إلى offline notifications rabbitmq؟

أنظمة الإشعارات Real-Time المبنية على WebSockets أو Push Notifications تعمل بشكل ممتاز عندما يكون المستخدم متصلًا، لكن في الواقع:

  • مستخدم التطبيق قد يغلق التطبيق كليًا.
  • الاتصال بالإنترنت قد ينقطع فجأة.
  • تطبيق الويب قد يتم إغلاقه أو تبويب المتصفح يتجمد.

في هذه الحالات، إذا أرسلت إشعارًا لحظيًا فقط، فسيُفقد عند عدم وجود اتصال. لذلك تحتاج إلى:

  1. كشف حالة المستخدم (Online/Offline).
  2. تخزين الإشعارات للمستخدمين غير المتصلين في Storage دائم أو شبه دائم.
  3. إعادة إرسال الإشعارات عند عودة المستخدم أو فتحه للتطبيق.

RabbitMQ هنا ليس DB، لكنه Message Broker يستخدم لتنظيم تدفق الرسائل والإشعارات بين الخدمات، والتكامل مع قواعد البيانات وخدمات الإرسال (WebSocket / Push / Email).

نظرة معمارية: كيف نبني نظام Offline Notifications باستخدام RabbitMQ؟

قبل التفاصيل، دعنا نرسم الصورة العامة (High-Level Architecture) لنظام offline notifications rabbitmq:

  • Notification Producer: أي خدمة أو مايكروسيرفس تنتج الإشعارات (New Message, New Comment, System Alert…).
  • RabbitMQ Exchange: نقطة توزيع الإشعارات على الـ Queues المختلفة حسب نوع الإشعار أو المستخدم.
  • Notification Service: خدمة مسؤولة عن:
    • التقاط الرسائل من RabbitMQ
    • معرفة حالة المستخدم
    • إرسال الإشعار إذا كان Online
    • تخزين الإشعار إذا كان Offline
  • Storage: قاعدة بيانات (Relational أو NoSQL) أو حتى Cache (مثل Redis) لتخزين الإشعارات غير المقروءة / غير المرسلة.
  • Delivery Channel: مثل WebSockets، FCM push، Email، أو SMS.

إذا كنت مهتمًا ببناء نظام إشعارات لحظي باستخدام RabbitMQ و WebSockets، راجع هذا الشرح: بناء نظام إشعارات Real-Time باستخدام RabbitMQ وWebSockets خطوة بخطوة.

تحديد حالة المستخدم: Online أم Offline؟

الخطوة الأولى في التعامل مع offline notifications rabbitmq هي فهم كيف تعرف أن المستخدم Online أو Offline. الخيارات الشائعة:

1. متابعة اتصال WebSocket

في تطبيقات الويب أو الموبايل التي تستخدم WebSocket:

  • عند فتح اتصال WebSocket من العميل، يمكنك:
    • تسجيل المستخدم كـ Online في نظام Presence (مثل Redis أو DB).
  • عند إغلاق الاتصال (onClose أو onDisconnect):
    • تحدّث حالة المستخدم إلى Offline.

عادةً يتم حفظ حالة المستخدم في:

  • Redis باستخدام Key مثل: user:{id}:status = online/offline
  • أو في جدول بسيط في قاعدة البيانات: user_status.

2. استخدام Heartbeat أو Ping

إذا لم يكن لديك WebSocket، يمكنك الاعتماد على:

  • استدعاءات API دورية من التطبيق (Ping) لتحديث آخر نشاط last_seen_at.
  • اعتبار المستخدم Offline إذا لم يُرسل أي طلب خلال X دقائق.

المهم هو أن Notification Service عندما تستقبل رسالة من RabbitMQ تستطيع أن تسأل: “هل هذا المستخدم متصل الآن؟”.

تصميم الـ Queue و Exchange للإشعارات في RabbitMQ

للتعامل مع الإشعارات، يمكنك استخدام واحد من الأنماط الشائعة في RabbitMQ:

  • Topic Exchange: لتوجيه الإشعارات حسب نوعها أو حسب المستخدم / المجموعة.
  • Direct Exchange: إذا كان التوجيه مباشرًا وبسيطًا.
  • Fanout Exchange: لبث الإشعارات الجماعية (Broadcast).

للتطبيق البسيط، يمكن اعتماد تصميم مثل:

  • Exchange: notifications.exchange من نوع topic.
  • Routing Key: user.<userId>.notification.
  • Queue عامة: notifications.queue تستهلك منها Service واحدة مسؤولة عن توزيع الإشعارات.

لشرح معمق لتصميم أنماط Pub/Sub في RabbitMQ، يمكنك مراجعة: RabbitMQ Pub/Sub Pattern: بناء نظام Broadcast للإشعارات الجماعية.

المنطق الأساسي: متى نخزن ومتى نرسل مباشرة؟

عندما تستقبل Notification Service رسالة من RabbitMQ، يمكن أن يكون منطق المعالجة كالتالي:

  1. قراءة الرسالة من Queue – مثلاً Payload:
    • user_id
    • title
    • body
    • type (chat, system, comment...)
    • created_at
  2. استعلام حالة المستخدم (Online/Offline) من Redis أو DB.
  3. إذا كان Online:
    • إرسال الإشعار فورًا عبر WebSocket أو Push أو أي قناة محددة.
    • اختياريًا: تخزينه في DB كـ "Unread" لأغراض الـ History.
  4. إذا كان Offline:
    • تخزين الإشعار في جدول أو Document مثل user_notifications بحالة pending أو not_delivered.
    • عدم محاولة الإرسال الآن.
  5. تأكيد استهلاك الرسالة في RabbitMQ (ACK) بعد نجاح التخزين / الإرسال.

بهذه الطريقة، يصبح RabbitMQ قناة مرور (Transport) بينما التخزين الفعلي يتم في قاعدة البيانات المناسبة.

أين نخزن الإشعارات للمستخدمين غير المتصلين؟

خيارات التخزين تعتمد على متطلباتك:

1. قاعدة بيانات Relational (مثل PostgreSQL أو MySQL)

مناسبة عندما:

  • تريد إمكانيات استعلام معقدة (Filter, Sort, Pagination).
  • تريد ربط الإشعار بكيانات أخرى (User, Order, Post…).

مثال جدول بسيط:

user_notifications (
  id               BIGINT PK,
  user_id          BIGINT,
  title            VARCHAR,
  body             TEXT,
  type             VARCHAR,
  status           ENUM('pending', 'delivered', 'read'),
  created_at       TIMESTAMP,
  delivered_at     TIMESTAMP NULL,
  read_at          TIMESTAMP NULL
)

2. NoSQL (مثل MongoDB)

مفيد عندما:

  • عدد الإشعارات ضخم وتحتاج إلى مرونة في الـ Schema.
  • تريد تخزين Payload كبير أو Dynamic.

3. Redis / In-Memory Storage

يمكن استخدامه كطبقة تخزين مؤقت (Cache) للإشعارات الأخيرة، مع ترحيل دوري (Migration) للإشعارات القديمة إلى DB دائمة.

كيف نعيد إرسال الإشعارات عند اتصال المستخدم؟

عندما يعود المستخدم للتطبيق، نحتاج إلى:

  • جلب الإشعارات التي تم تخزينها له أثناء غيابه.
  • إرسالها على الفور عبر قناة مناسبة (عادة WebSocket أو API Response).
  • تحديث حالتها إلى delivered.

السيناريو 1: عبر WebSocket

عند فتح اتصال WebSocket جديد:

  1. يتم توثيق المستخدم (JWT أو Token).
  2. بعد نجاح التوثيق، تستدعي الخدمة دالة:
    • fetchPendingNotifications(userId)
  3. ترجع كل الإشعارات بحالة pending أو not_delivered.
  4. إرسال هذه الإشعارات دفعة واحدة أو على شكل Stream عبر WebSocket.
  5. تحديث حالتها إلى delivered مع Timestamp.

السيناريو 2: عبر REST API

يمكن أن يكون لديك Endpoint مثل:

GET /api/notifications?status=pending

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

استخدام RabbitMQ كـ Trigger لإعادة الإرسال

إضافة إلى WebSocket/API، يمكنك استخدام RabbitMQ كـ Trigger عند تغيير حالة المستخدم من Offline إلى Online:

  • عندما تتغير حالة المستخدم إلى Online في خدمة Presence:
    • يرسل Event إلى RabbitMQ مثلاً: user.<userId>.status.online
  • تستهلك Notification Service هذا الحدث:
    • تستدعي fetchPendingNotifications(userId).
    • ترسل كل الإشعارات المعلقة عبر WebSocket أو Push.
    • تحدّث حالتها في DB.

بهذه الطريقة يكون RabbitMQ جزءًا من مسار استرجاع الإشعارات بالإضافة إلى مسار إنتاج الإشعارات.

التعامل مع الفشل وإعادة المحاولة (Retry & Dead Letter Queue)

في أنظمة الإشعارات، الفشل وارد عند:

  • حفظ الإشعار في قاعدة البيانات.
  • إرساله عبر Push Service أو WebSocket.

هنا يأتي دور:

  • Retry Mechanism: إعادة المحاولة عدة مرات مع Backoff.
  • Dead Letter Queue (DLQ): لنقل الرسائل الفاشلة بعد عدد معين من المحاولات.

لتطبيق هذا المفهوم بالتفصيل على RabbitMQ و Kafka، يمكنك مراجعة: كيفية التعامل مع الرسائل الفاشلة في Kafka وRabbitMQ باستخدام Dead Letter Queue.

نصائح تصميمية مهمة لنظام offline notifications rabbitmq

1. لا تستخدم RabbitMQ كبديل عن قاعدة البيانات

RabbitMQ ليس Storage طويل الأمد. هدفه تمرير الرسائل بسرعة وأمان بين الخدمات. دائمًا:

  • استهلك الرسالة من Queue.
  • خزن البيانات المهمة (مثل الإشعارات) في DB.
  • ثم قم بإرسال ACK.

2. افصل بين طبقة الإرسال وطبقة التخزين

اجعل هناك طبقتين واضحتين في Notification Service:

  • Delivery Layer: مسؤولة عن اختيار قناة الإرسال (WebSocket, Push, Email…).
  • Storage Layer: مسؤولة عن حفظ حالة الإشعارات (pending, delivered, read).

هذا الفصل يسهل تطوير النظام وزيادة قنوات الإرسال دون تغيير منطق التخزين.

3. راقب أداء النظام وقابلية التوسع

مع زيادة عدد المستخدمين والإشعارات، تحتاج إلى:

  • تجزئة الـ Queues أو استخدام Work Queues متعددة.
  • توسيع عدد الـ Consumers (Horizontal Scaling) لخدمة الإشعارات.
  • توزيع الأحمال على أكثر من Instance من RabbitMQ إذا لزم الأمر.

للتعمق في تصميم أنظمة Notifications قابلة للتوسع، راجع: تصميم نظام Notifications قابل للتوسع باستخدام Message Queues.

مثال مبسط لتدفق إشعار لمستخدم Offline ثم Online

لنفترض أن لدينا مستخدمًا (User A) غير متصل حاليًا:

  1. يحدث حدث في النظام (مثل وصول رسالة جديدة).
  2. خدمة Chat Service ترسل رسالة إلى notifications.exchange مع Routing Key: user.123.notification.
  3. تلتقط Notification Service الرسالة من notifications.queue.
  4. تسأل خدمة Presence عن حالة المستخدم 123 → النتيجة: Offline.
  5. تكتب الإشعار في user_notifications بحالة pending.
  6. بعد ساعة، يفتح المستخدم التطبيق، يتصل عبر WebSocket.
  7. خدمة WebSocket تحدث حالة المستخدم إلى Online، وترسل Event إلى RabbitMQ (اختياري).
  8. تستدعي Notification Service دالة fetchPendingNotifications(123).
  9. ترجع كل الإشعارات المعلقة، يتم إرسالها عبر WebSocket دفعة واحدة.
  10. يتم تحديث حالتها في DB إلى delivered.

بهذا الشكل، لا يفقد المستخدم أي إشعار، حتى لو كان غير متصل وقت الإرسال الأصلي.

دمج offline notifications rabbitmq مع Microservices

في بيئة Microservices، غالبًا ما يكون لديك أكثر من خدمة تنتج إشعارات (Orders, Chat, Billing…). أفضل مبدأ:

  • خدمات الأعمال (Business Services) لا ترسل الإشعارات بنفسها.
  • بدلاً من ذلك، ترسل Events إلى RabbitMQ.
  • خدمة مستقلة Notification Service تستهلك الأحداث وتقرر:
    • هل المستخدم متصل؟
    • كيف يتم إرسال الإشعار؟
    • هل يتم تخزينه كـ Offline Notification؟

لشرح كامل لتصميم Notification Service باستخدام RabbitMQ في بيئة Microservices يمكنك الرجوع إلى: تصميم Notification Service باستخدام RabbitMQ في Microservices.

خلاصة

بناء نظام offline notifications rabbitmq ليس مجرد وضع الرسائل في Queue؛ بل هو تصميم متكامل يشمل:

  • تتبع حالة المستخدم (Online / Offline).
  • تخزين الإشعارات للمستخدمين غير المتصلين في Storage مناسب (DB / NoSQL / Redis).
  • استخدام RabbitMQ كقناة موثوقة لتمرير الإشعارات بين الخدمات.
  • إعادة إرسال الإشعارات عند اتصال المستخدم، عبر WebSocket أو REST API.
  • تطبيق آليات Retry وDead Letter Queue لضمان عدم فقدان الرسائل.

باستخدام هذه الأسس، يمكنك بناء نظام إشعارات مرن، موثوق، وقابل للتوسع، يضمن أن جميع المستخدمين يحصلون على إشعاراتهم حتى لو كانوا Offline عند لحظة الإرسال الأولى.

حول المحتوى:

شرح كيفية التعامل مع المستخدمين غير المتصلين وتخزين الإشعارات حتى عودتهم.

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

أضف تعليقك