طلبات المستخدمين كانت تنتظر في طابور لا ينتهي: كيف أنقذتني ‘قوائم انتظار الرسائل’ (Message Queues) من جحيم تجربة المستخدم البطيئة؟

يا جماعة الخير، السلام عليكم ورحمة الله. معكم أخوكم أبو عمر.

قبل كم سنة، كنت شغال على مشروع لمنصة اجتماعية جديدة، وكان فيها ميزة تسمح للمستخدمين برفع صور وفيديوهات. في البداية، والأعضاء لسا قلال، كانت الأمور “عال العال”. لكن مع زيادة عدد المستخدمين، بدأت الشكاوى توصلني زي المطر: “التطبيق بطيء!”، “بستنى دقيقة كاملة عشان أرفع صورة!”، “علّق معي التطبيق وما رضي يكمل!”.

في ليلة من الليالي، وأنا قاعد بقلّب في سجلات الخادم (Server Logs)، شفت المصيبة بعيني. كل طلب رفع فيديو كان “يحجز” الخادم لمدة طويلة وهو يعمل معالجة للفيديو (يضغط حجمه، يغير صيغته، وياخذ منه صورة مصغرة). خلال هاي الفترة، أي طلب ثاني من نفس المستخدم أو من مستخدمين ثانيين كان يضطر ينتظر في طابور طويل. الخادم المسكين كان يشتغل بأقصى طاقته، والمستخدمين زهقوا من الانتظار. بصراحة، “ولّعت معي” وقتها وقلت لحالي: “يا أبو عمر، في إشي غلط جوهري هون، ولازم تلاقيله حل سريع”.

بعد بحث وسهر ليلتين ثلاث، تعرفت على صديقي الصدوق اللي أنقذ الموقف: مفهوم “قوائم انتظار الرسائل” أو الـ Message Queues. ومن يومها، صارت جزء لا يتجزأ من أي نظام كبير ببنيه. خلوني أحكيلكم الحكاية بالتفصيل.

ما هي “قوائم انتظار الرسائل” (Message Queues)؟ وليش هي مهمة؟

ببساطة شديدة، تخيل إنك رحت على مكتب بريد عشان ترسل طرد. هل بتضلك واقف جنب موظف البريد لحد ما يوصل الطرد للعنوان المطلوب؟ طبعاً لأ. أنت بتسلم الطرد للموظف (أو بتحطه في الصندوق)، وبتاخذ وصل، وبتروح تكمل يومك. مكتب البريد هو اللي بتكفل بالمهمة وبيوصل الطرد في الخلفية.

قوائم انتظار الرسائل هي تماماً مثل مكتب البريد لتطبيقاتك. هي عبارة عن وسيط بيستقبل “رسائل” (مهام) من جزء من تطبيقك، وبيخزنها في طابور، عشان جزء ثاني من التطبيق يسحبها وينفذها وقت ما يكون فاضي.

هذا النظام البسيط يتكون من ثلاثة أجزاء رئيسية:

  • المنتِج (Producer): هو الجزء من تطبيقك اللي بينشئ الرسالة (المهمة) وبيرسلها لقائمة الانتظار. في قصتي، كان هو الكود اللي بيستقبل طلب رفع الفيديو من المستخدم.
  • قائمة الانتظار (Queue): هي “صندوق البريد” اللي بيخزن الرسائل بشكل مؤقت وبيرتبها في طابور (عادةً الأول في الدخول هو الأول في الخروج – FIFO).
  • المستهلِك (Consumer): هو جزء منفصل من تطبيقك (ممكن يكون خادم ثاني خالص) وظيفته يراقب قائمة الانتظار، وكل ما يلاقي رسالة جديدة، يسحبها وينفذ المهمة اللي فيها (مثل معالجة الفيديو).

الفكرة الجوهرية هنا هي “فصل” (Decoupling) المنتِج عن المستهلِك. المنتِج ما بيهمه مين راح ينفذ المهمة أو متى، كل اللي عليه يعمله هو إنه يرمي الرسالة في الطابور ويكمل شغله.

الحياة قبل وبعد قوائم الانتظار: مقارنة بسيطة

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

السيناريو المتزامن (Synchronous): الطريق إلى الجحيم

هذا هو الوضع اللي كنت فيه بالبداية. المستخدم يضغط على زر “رفع”، والتطبيق بيمشي بالخطوات التالية:

  1. المستخدم يرسل طلب HTTP إلى الخادم ومعه الفيديو.
  2. الخادم يستقبل الطلب ويبدأ فوراً بمعالجة الفيديو (مهمة بطيئة جداً).
  3. المستخدم يظل منتظراً وشاشة التحميل تدور أمامه.
  4. بعد 50 ثانية (مثلاً)، الخادم ينتهي من المعالجة.
  5. الخادم يرسل استجابة للمستخدم “تم الرفع بنجاح”.

المشاكل الكارثية لهذا الأسلوب:

  • تجربة مستخدم سيئة جداً: لا أحد يحب الانتظار.
  • استنزاف موارد الخادم: الخادم الرئيسي مشغول بعمليات بطيئة بدل من خدمة طلبات سريعة أخرى.
  • انعدام المرونة: لو فشلت عملية المعالجة في المنتصف، يفشل الطلب كله وقد يضطر المستخدم للبدء من جديد.

السيناريو اللامتزامن (Asynchronous): النجاة والراحة

هنا يأتي دور بطلنا، الـ Message Queue:

  1. المستخدم يرسل طلب HTTP إلى الخادم ومعه الفيديو.
  2. الخادم يستقبل الطلب، يقوم بعمليات تحقق سريعة (مثل حجم الملف، نوعه)، ثم ينشئ “رسالة” تحتوي على معلومات المهمة (مثل: “عالج الفيديو الموجود في المسار X”).
  3. الخادم يرسل هذه الرسالة إلى قائمة الانتظار (Queue). هذه العملية تأخذ أجزاء من الثانية.
  4. الخادم يرسل فوراً استجابة للمستخدم: “تم استلام طلبك وجاري معالجته. سنعلمك عند الانتهاء”.
  5. المستخدم يكمل تصفح التطبيق بسعادة.
  6. في مكان آخر (خادم آخر أو عملية أخرى في الخلفية)، يوجد “مستهلِك” (Consumer) يسحب المهمة من الطابور ويبدأ بمعالجة الفيديو البطيئة.
  7. عندما ينتهي المستهلِك، يمكنه إرسال إشعار للمستخدم (Notification) أو تحديث حالة الفيديو في قاعدة البيانات.

شفتوا الفرق؟ حولنا عملية كانت تستغرق 50 ثانية من وقت المستخدم إلى أقل من ثانية واحدة! وهذا هو سحر البرمجة اللامتزامنة.

متى لازم تفكر تستخدم قوائم الانتظار؟ (علامات الخطر)

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

  • المهام طويلة الأمد (Long-running tasks): أي عملية تأخذ أكثر من نصف ثانية أو ثانية لتنفيذها هي مرشح مثالي. أمثلة:
    • معالجة الصور والفيديوهات.
    • إنشاء تقارير معقدة أو ملفات PDF/Excel.
    • الاتصال بخدمات خارجية (APIs) قد تكون بطيئة.
  • المهام التي لا تحتاج لرد فوري: مثل إرسال رسائل بريد إلكتروني ترحيبية أو إشعارات. المستخدم لا يحتاج أن ينتظر حتى يتم إرسال الإيميل فعلياً.
  • التواصل بين الخدمات المصغرة (Microservices): إذا كان تطبيقك مقسم لخدمات صغيرة، فقوائم الانتظار هي طريقة ممتازة لتتواصل هذه الخدمات مع بعضها البعض بدون ما تعتمد كل خدمة على أن تكون الأخرى شغالة في نفس اللحظة.
  • التعامل مع تدفقات ضخمة ومفاجئة من البيانات (Handling Bursts): تخيل أنك أطلقت حملة تسويقية وجاءك 10,000 مستخدم جديد يسجلون في نفس الدقيقة. بدلاً من أن ينهار خادمك تحت الضغط، يمكنك وضع كل طلب تسجيل في طابور، ويقوم المستهلك بمعالجتها بهدوء وبالسرعة اللي بيقدر عليها. هذا يسمى “تخفيف الحمل” (Load Leveling).

أشهر اللاعبين في الساحة: RabbitMQ و Kafka

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

RabbitMQ: ساعي البريد الذكي والتقليدي

RabbitMQ هو ما نسميه “وسيط رسائل” (Message Broker) تقليدي. هو مثل ساعي بريد ذكي جداً، عنده قدرات توجيه (Routing) متقدمة. أنت تعطيه الرسالة، وهو يتكفل بتوصيلها للطابور الصحيح بناءً على قواعد أنت تحددها. إنه خيار ممتاز ومستقر جداً لمعظم الحالات التقليدية.

متى تستخدمه؟ مثالي للمهام في الخلفية، وللتواصل بين الخدمات المصغرة اللي بتحتاج منطق توجيه معقد.

مثال بسيط جداً (بايثون مع مكتبة pika) لإرسال رسالة:


import pika

# الاتصال بـ RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# التأكد من وجود الطابور، إذا لم يكن موجوداً، يتم إنشاؤه
channel.queue_declare(queue='video_processing')

# الرسالة التي نريد إرسالها (يمكن أن تكون بصيغة JSON)
message_body = '{"video_id": 123, "path": "/uploads/video.mp4"}'

# نشر الرسالة في الطابور المحدد
channel.basic_publish(exchange='',
                      routing_key='video_processing',
                      body=message_body)

print(" [x] Sent 'Process Video 123'")
connection.close()

Apache Kafka: النهر المتدفق للبيانات الضخمة

كافكا هو وحش مختلف. هو ليس مجرد طابور رسائل، بل هو “منصة تدفق أحداث موزعة” (Distributed Event Streaming Platform). تخيله كنهر ضخم من البيانات لا يتوقف. الرسائل في كافكا لا تُحذف بعد استهلاكها (إلا بعد فترة أنت تحددها)، مما يسمح لعدة مستهلكين بقراءة نفس البيانات لأغراض مختلفة (مثلاً، مستهلك لتحليل البيانات، وآخر لأرشفتها).

متى تستخدمه؟ مثالي لتجميع السجلات (Log Aggregation)، والتحليلات في الزمن الحقيقي (Real-time Analytics)، وتتبع الأنشطة، وعندما يكون لديك كميات هائلة من البيانات (Big Data) تحتاج لمعالجتها بشكل متدفق.

مثال بسيط جداً (بايثون مع مكتبة kafka-python) لإرسال رسالة:


from kafka import KafkaProducer
import json

# إنشاء منتِج يتصل بكافكا
producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

# الرسالة التي نريد إرسالها
message = {'event': 'user_signed_up', 'user_id': 456}

# إرسال الرسالة إلى "موضوع" (Topic) معين في كافكا
producer.send('user_events', value=message)
producer.flush() # التأكد من إرسال كل الرسائل المعلقة

print(" [x] Sent event 'user_signed_up'")

نصائح من دفتر أبو عمر

بعد سنين من التعامل مع هذه الأنظمة، تعلمت كم درس “على جلدي”. اسمحولي أشارككم أهمها:

  • ابدأ بسيطاً: لا تركض وراء كافكا من أول يوم لمجرد أنه “موضة”. في 80% من الحالات، نظام أبسط مثل RabbitMQ أو حتى طابور يعتمد على Redis (مثل Celery في بايثون) يكون أكثر من كافٍ وسهل الإدارة. الشغلة مش مستاهلة تعقيد زيادة.
  • التعامل مع الفشل (Poison Pills): ماذا لو فشل المستهلك في معالجة رسالة مراراً وتكراراً؟ هذه “الرسالة المسمومة” قد تعطل الطابور كله. جهّز دائماً “طابور الرسائل الميتة” (Dead-Letter Queue – DLQ). بعد عدد معين من المحاولات الفاشلة، انقل الرسالة إلى الـ DLQ لتحليلها لاحقاً بشكل يدوي.
  • اجعل مهامك قابلة للتكرار بأمان (Idempotency): بسبب مشاكل الشبكة أو إعادة التشغيل، قد يتم تنفيذ نفس الرسالة أكثر من مرة. صمم منطق المستهلك الخاص بك بحيث لو استلم نفس الرسالة مرتين، لا يسبب ذلك مشكلة. مثلاً، قبل إضافة سجل جديد في قاعدة البيانات، تحقق أولاً إذا كان موجوداً.
  • المراقبة والإنذار (Monitoring & Alerting): راقب دائماً طول الطابور. إذا كان الطابور ينمو بشكل مستمر، فهذا يعني أن المنتجين أسرع من المستهلكين، وتحتاج لإضافة المزيد من المستهلكين (Scale out). اضبط تنبيهات (Alerts) لتخبرك إذا زاد طول الطابور عن حد معين.

الخلاصة: لا تخلي المستخدم يستنى! 🚀

من الآخر، المبدأ الأساسي هو: لا تجعل المستخدم النهائي ينتظر أي عملية ليست ضرورية للاستجابة الفورية. طلب المستخدم هو شيء مقدس، ويجب أن يكون الرد عليه سريعاً وخفيفاً قدر الإمكان.

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

نصيحتي الأخيرة: في مشروعك القادم، عندما تواجهك مهمة تأخذ وقتاً، توقف لحظة واسأل نفسك: “هل يجب على المستخدم أن ينتظر هذا؟”. إذا كان الجواب “لا”، فأنت تعرف الآن ما هو الحل. بالتوفيق يا جماعة الخير.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التوظيف وبناء الهوية التقنية

ملفي الشخصي على GitHub كان مدينة أشباح: كيف أنقذني ‘المشروع الجانبي الهادف’ من جحيم التجاهل؟

أشارككم قصتي مع ملف GitHub المهجور وكيف حوّلته من مدينة أشباح إلى نقطة جذب لمسؤولي التوظيف. اكتشف معي استراتيجية "المشروع الجانبي الهادف" التي غيرت مسيرتي...

4 أبريل، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

حساباتي البنكية كانت جزرًا معزولة: كيف أنقذتني ‘الخدمات المصرفية المفتوحة’ من جحيم إدارة الأموال اليدوية؟

أنا أبو عمر، مطور برمجيات فلسطيني، وهذه قصتي مع إدارة الأموال اليدوية التي كانت كابوسًا شهريًا. سأشارككم كيف حولت "الخدمات المصرفية المفتوحة" (Open Banking) هذا...

3 أبريل، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

طلباتي كانت تختفي بين الخدمات: كيف أنقذني ‘التتبع الموزع’ (Distributed Tracing) من جحيم تحليل الأعطال؟

أشارككم قصة حقيقية عن طلبات كانت تضيع في أنظمتنا المعقدة، وكيف كان التتبع الموزع (Distributed Tracing) هو المنقذ. سنتعمق في هذا المفهوم، من هو ولماذا...

3 أبريل، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

فريقي كان يخشى قول ‘لا أعرف’: كيف أنقذتني ‘السلامة النفسية’ من جحيم الأخطاء الصامتة؟

أشارككم قصة حقيقية من مسيرتي كقائد فريق تقني، حين كاد الخوف من الاعتراف بالجهل أن يكلفنا الكثير. اكتشفوا معي مفهوم "السلامة النفسية" وكيف حوّل فريقنا...

3 أبريل، 2026 قراءة المزيد
اختبارات الاداء والجودة

خدماتي كانت تتحدث لغات مختلفة: كيف أنقذني اختبار العقود (Contract Testing) من جحيم التكامل الهش؟

في عالم الخدمات المصغرة، يمكن أن يتحول التكامل بين الخدمات إلى كابوس. أشارككم قصة من تجربتي وكيف أنقذني "اختبار العقود" (Contract Testing) من هذا الجحيم،...

3 أبريل، 2026 قراءة المزيد
أتمتة العمليات

تنبيهاتي كانت تضيع في بحر الإيميلات: كيف أنقذني ChatOps من فوضى إدارة الحوادث والنشر؟

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

3 أبريل، 2026 قراءة المزيد
البودكاست