طلبات المستخدمين كانت تضيع أثناء الذروة: كيف أنقذتني طوابير الرسائل (Message Queues) من فقدان البيانات؟

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

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

بتذكر هذاك اليوم زي كأنه مبارح. كنت قاعد في المكتب، بشرب فنجان القهوة ومبسوط على الأرقام اللي عمالها بتزيد بشكل جنوني. فجأة، بدأت توصلني تنبيهات من نظام المراقبة: “High CPU Load”, “Database Connection Timeout”. وفوقها، رسائل من فريق الدعم الفني: “يا أبو عمر، المستخدمين بشتكوا، طلباتهم بتعلّق!”، “في ناس دفعت وموصلهاش تأكيد!”.

شعور العجز في هذيك اللحظة لا يوصف. بتحس كأنك بنيت سفينة قوية، وفجأة موجة واحدة كبيرة هدت كل شيء. كنا زي اللي بحاول يطفي حريقة بكاسة مي. كل ما نحل مشكلة، تطلع عشرة غيرها. المشكلة الأكبر كانت إنه في بيانات فعلًا كانت عم تضيع! طلبات مستخدمين مهمة، عمليات دفع، تسجيل… كلها كانت “بتضيع بالطوشة” بسبب الضغط الهائل.

في عز هاي المعمعة، وأنا بحاول أفهم شو اللي بصير، تذكرت محاضرة حضرتها زمان عن “المعمارية غير المتزامنة” (Asynchronous Architecture). وقتها لمعت في بالي فكرة: “ليش إحنا بنجبر المستخدم يستنى كل هاي العمليات المعقدة لتخلص؟ ليش ما ناخد طلبه ونحكيله “وصل يا غالي، طلبك قيد التنفيذ”، ونشتغل عليه براحتنا في الخلفية؟”. ومن هنا، كانت بداية رحلتي مع طوابير الرسائل (Message Queues)، الحل اللي أنقذ الموقف حرفيًا.

المشكلة الحقيقية: عنق الزجاجة في المعمارية المتزامنة

قبل ما نغوص في الحل، خلينا نفهم أصل المشكلة. معظم التطبيقات في بدايتها تُبنى بمعمارية متزامنة (Synchronous). شو يعني؟

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

هذا بالضبط اللي كان يصير معنا. كان طلب المستخدم الواحد (HTTP Request) مسؤول عن:

  1. التحقق من صحة البيانات.
  2. حفظ البيانات في قاعدة البيانات الرئيسية.
  3. إرسال إيميل تأكيد.
  4. إرسال إشعار (Push Notification).
  5. تحديث بعض الإحصائيات في قاعدة بيانات تحليلية أخرى.

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

الحل السحري: طوابير الرسائل (Message Queues)

هنا يأتي دور طوابير الرسائل. ببساطة شديدة، طابور الرسائل هو وسيط، زي ساعي البريد الذكي، بيقعد بين أجزاء نظامك المختلفة.

بدل ما الخادم (Web Server) يعمل كل الشغل بنفسه، صار يعمل شغلة وحدة بسيطة وسريعة: ياخد الطلب من المستخدم، ويحطه على شكل “رسالة” في طابور، وبعدين يرد على المستخدم فورًا: “تمام، استلمنا طلبك وجاري معالجته”.

في الخلفية، عنا “عمال” (Workers) منفصلين، كل شغلتهم في الحياة إنهم يسحبوا الرسائل من هذا الطابور بهدوء وروقان، وينفذوا المهام المطلوبة وحدة ورا الثانية.

المعمارية الجديدة صارت كالتالي:

  • المنتِج (Producer): هو الخادم اللي بستقبل طلبات المستخدمين. وظيفته بس يجهز رسالة (مثلًا: { "user_id": 123, "task": "send_welcome_email" }) ويرميها في الطابور.
  • الطابور (Queue): هو الوسيط اللي بخزن الرسائل بالترتيب. الحلو فيه إنه بيضمن عدم ضياع الرسائل حتى لو “العمال” كانوا مشغولين أو مطفيين.
  • المستهلِك (Consumer): هو “العامل” اللي بيسحب الرسائل من الطابور وينفذ المهمة الفعلية (إرسال الإيميل، معالجة الصورة، …إلخ).

هذا الفصل (Decoupling) بين المكونات هو جوهر القوة. الخادم ما عاد يهتم كيف أو متى سيتم إرسال الإيميل. كل اللي عليه يعمله هو تسليم البريد لساعي البريد الموثوق (الطابور).

ليش هذا الحل فعال جدًا؟

  • تحسين تجربة المستخدم: المستخدم يحصل على استجابة فورية. ما في داعي يستنى العمليات الطويلة.
  • الموثوقية وعدم فقدان البيانات: لو “العامل” اللي بيرسل الإيميلات تعطل فجأة، الرسائل بتضل محفوظة بأمان في الطابور. بس يرجع يشتغل، بكمل شغله من وين ما وقف. هذا هو مربط الفرس في حل مشكلة “ضياع الطلبات”.
  • موازنة الأحمال (Load Leveling): أثناء الذروة، الطابور بيمتلئ بالرسائل، والعمال بضلوا يشتغلوا عليها بهدوء حسب قدرتهم الاستيعابية. الطابور امتص الصدمة بدل ما يمتصها الخادم الرئيسي.
  • قابلية التوسع (Scalability): حاسس إنه الطابور عماله بيطول والرسائل بتتراكم؟ بسيطة! بدل ما تشغل “عامل” واحد، شغل عشرة أو مية عامل. بتقدر تزيد عدد المستهلكين بسهولة لمواكبة الضغط.

من أرض المعركة: كيف طبقنا الحل خطوة بخطوة

الكلام النظري حلو، بس خلينا نشوف كيف طبقنا هذا الحكي على أرض الواقع.

1. اختيار الأداة المناسبة: RabbitMQ أم Kafka؟

أول قرار كان لازم ناخده هو اختيار تقنية طابور الرسائل. أشهر لاعبين في السوق هما RabbitMQ و Kafka.

  • RabbitMQ: هو الوسيط التقليدي للرسائل (Message Broker). مرن جدًا، يدعم بروتوكولات متعددة، وعنده أنماط توجيه (Routing) معقدة ومفيدة. بفكر فيه زي مكتب بريد ذكي جدًا بيعرف يفرز الرسائل ويوجهها للصناديق الصحيحة.
  • Apache Kafka: هو أكثر من مجرد طابور رسائل، هو منصة تدفق بيانات موزعة (Distributed Streaming Platform). مصمم للتعامل مع كميات هائلة من البيانات (Throughput) وتخزينها لفترات طويلة. بفكر فيه زي نهر ضخم من البيانات بيجري بسرعة وثبات.

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

2. مثال كود بسيط (باستخدام Python و RabbitMQ)

لتقريب الصورة، هي مثال بسيط جدًا بلغة بايثون ومكتبة pika لكيفية عمل المنتج والمستهلك.

المنتِج (Producer) – الخادم الذي يرسل المهمة

هذا الكود بيحطه في الخادم عندك. لما يوصلك طلب جديد، بتستدعي هاي الوظيفة.


# producer.py
import pika
import json

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

# نتأكد من وجود الطابور، إذا مش موجود، بيتم إنشاؤه
channel.queue_declare(queue='task_queue', durable=True) # durable=True بتخلي الطابور والرسائل تنجو من إعادة تشغيل السيرفر

def send_task_to_queue(task_data):
    """
    وظيفة لإرسال مهمة إلى طابور الرسائل
    """
    message = json.dumps(task_data) # نحول البيانات لـ JSON

    channel.basic_publish(
        exchange='',
        routing_key='task_queue', # اسم الطابور اللي رح نرسل عليه
        body=message,
        properties=pika.BasicProperties(
            delivery_mode=2,  # نجعل الرسالة persistent (تُحفظ على القرص)
        ))
    print(f" [x] تم إرسال المهمة: {message}")

# مثال على الاستخدام
task = {"user_id": 456, "task": "generate_invoice", "amount": 99.9}
send_task_to_queue(task)

connection.close()

المستهلِك (Consumer) – العامل الذي ينفذ المهمة

هذا الكود بتشغله كخدمة منفصلة (service) في الخلفية. ممكن تشغل منه عدة نسخ على عدة سيرفرات.


# consumer.py
import pika
import time
import json

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

channel.queue_declare(queue='task_queue', durable=True)
print(' [*] في انتظار الرسائل. للخروج اضغط CTRL+C')

def process_task(task_data):
    """
    هنا يتم تنفيذ المهمة الحقيقية
    """
    print(f"  - جاري معالجة المهمة للمستخدم: {task_data.get('user_id')}")
    if task_data.get('task') == 'generate_invoice':
        # هنا تضع كود إنشاء الفاتورة وإرسالها
        time.sleep(5) # محاكاة لعملية تأخذ وقتاً
    print(f"  - تم الانتهاء من المهمة.")


def callback(ch, method, properties, body):
    """
    هذه الوظيفة يتم استدعاؤها مع كل رسالة جديدة
    """
    print(f" [x] تم استلام رسالة: {body.decode()}")
    
    try:
        task_data = json.loads(body)
        process_task(task_data)
        
        # بعد الانتهاء بنجاح، نرسل تأكيد (acknowledgment) لـ RabbitMQ
        # ليقوم بحذف الرسالة من الطابور
        ch.basic_ack(delivery_tag=method.delivery_tag)
        
    except Exception as e:
        print(f"حدث خطأ أثناء معالجة الرسالة: {e}")
        # هنا يمكن إضافة منطق لإعادة المحاولة أو إرسال الرسالة لطابور الأخطاء
        # ch.basic_nack(delivery_tag=method.delivery_tag) # لإخبار RabbitMQ أن المعالجة فشلت


# نخبر RabbitMQ ألا يرسل رسالة جديدة للعامل إلا بعد أن ينتهي من الحالية
channel.basic_qos(prefetch_count=1) 
channel.basic_consume(queue='task_queue', on_message_callback=callback)

channel.start_consuming()

نصائح من الخبّاز: دروس تعلمتها بالطريقة الصعبة

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

  1. اجعل عملياتك آمنة للتكرار (Idempotent): شو بصير لو العامل أخذ رسالة، نفذها، وبعدين تعطل قبل ما يبعت “تأكيد” لـ RabbitMQ؟ الطابور رح يعتبر إنه المهمة ما تنفذت، ورح يعطيها لعامل ثاني. لازم يكون الكود تبعك مصمم بحيث لو استلم نفس المهمة مرتين، ما يسبب مشكلة (مثلاً، ما يرسل نفس الفاتورة مرتين).
  2. استخدم طابور الرسائل الميتة (Dead Letter Queue): بعض الرسائل ممكن تفشل بشكل متكرر (مثلاً بسبب بيانات خاطئة). بدل ما تضل تحاول تنفيذها للأبد وتسد الطابور الرئيسي، اعمل “طابور للأخطاء” أو “رسائل ميتة”. أي رسالة بتفشل عدد معين من المرات، انقلها لهذا الطابور ليتم فحصها يدويًا لاحقًا.
  3. راقب ثم راقب ثم راقب: أهم مؤشر لازم تراقبه هو “طول الطابور” (Queue Length). إذا كان الطابور ينمو بشكل مستمر، هذا يعني أن المنتجين أسرع من المستهلكين، وأنت بحاجة لزيادة عدد “العمال” (المستهلكين).

الخلاصة: لا تخلي طلبات المستخدمين “تضيع بالطوشة” 💪

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

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

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

أبو عمر

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

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

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

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

آخر المدونات

صورة المقال
التكنلوجيا المالية Fintech

نظام مكافحة غسيل الأموال كان يطلق إنذارات على كل فنجان قهوة: كيف استخدمتُ نماذج الكشف عن الشذوذ (Anomaly Detection) للتركيز على المخاطر الحقيقية؟

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

20 مارس، 2026 قراءة المزيد
أتمتة العمليات

وظائف Cron كانت شبكة عنكبوت صامتة: كيف أنقذتني محركات تنسيق سير العمل من فوضى المهام المجدولة؟

أشارككم قصتي مع الفوضى التي سببتها مهام Cron المجدولة وكيف كانت بمثابة شبكة عنكبوت صامتة كادت أن تدمر أحد المشاريع. نستكشف معًا كيف أنقذتني أدوات...

19 مارس، 2026 قراءة المزيد
​معمارية البرمجيات

تغيير قاعدة البيانات كان يتطلب إعادة كتابة نصف التطبيق: كيف أنقذتني ‘المعمارية النظيفة’ (Clean Architecture) من هذا الكابوس؟

أشارككم قصة حقيقية من مسيرتي كمبرمج، حيث كاد قرار تغيير قاعدة البيانات أن يدمر مشروعًا بالكامل. سأشرح لكم كيف أنقذتني مبادئ "المعمارية النظيفة" (Clean Architecture)...

19 مارس، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كل زر بلون مختلف وكل أيقونة بقصة: كيف أنقذني ‘نظام التصميم’ (Design System) من فوضى الواجهات؟

أشارككم قصة من قلب المعركة، كيف انتقلنا من فوضى الألوان والأزرار المتضاربة في مشاريعنا إلى التناغم والكفاءة. هذه المقالة هي دليلك العملي لفهم وبناء "نظام...

18 مارس، 2026 قراءة المزيد
الحوسبة السحابية

كل نقرة في لوحة التحكم كانت قنبلة موقوتة: كيف أنقذتني ‘البنية التحتية كشيفرة’ (IaC) من كارثة محققة؟

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

17 مارس، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

مقابلاتي السلوكية كانت كارثة: كيف أنقذتني طريقة STAR من أسئلة ‘حدثنا عن موقف صعب…؟’

أشارككم تجربتي الشخصية مع المقابلات السلوكية وكيف تحولت إجاباتي من كارثية ومشتتة إلى قصص احترافية ومقنعة. اكتشفوا معي طريقة STAR، الأداة السحرية التي أنقذت مسيرتي...

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