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

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

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

يوم الإطلاق، الأمور كانت تمام… لأول نص ساعة بس. فجأة، وبدون سابق إنذار، “خربت الدنيا”. الإشعارات بلشت توصلنا زي المطر: “السيرفر واقع”، “التطبيق بعلّق”، “الصورة ما تحملت”. دخلت على لوحة المراقبة وشفت الكارثة: المعالجات (CPUs) واصلة 100%، والذاكرة شبه ممتلئة، وطلبات المستخدمين بتتكدس وبتعمل timeout قبل ما حتى تبدأ معالجتها. الأسوأ من هيك، إنه بعض الطلبات كانت بتضيع تماماً، المستخدم برفع الصورة وما بصير إشي، وكأنه طلبه تبخر في الهوا.

قعدنا كفريق نحاول نلاقي حل سريع، زدنا عدد السيرفرات، عملنا optimization للكود، بس كله كان زي اللي بحط لزقة على جرح بنزف. المشكلة كانت أعمق من هيك. بعد ليلة طويلة من القهوة والبحث والتحليل، وقفت وقلت للفريق: “يا جماعة، مشكلتنا مش في سرعة المعالجة، مشكلتنا إنه بنحاول نعمل كل إشي هلأ وفوراً. وهذا الأسلوب ما بنفع مع الأحمال العالية.”

وهون كانت نقطة التحول. الحل ما كان في زيادة القوة الحصانية للسيرفرات، بل في تغيير طريقة تفكيرنا بالكامل، والانتقال من المعالجة الفورية المتزامنة (Synchronous) إلى عالم المعالجة غير المتزامنة (Asynchronous) باستخدام بطل قصتنا اليوم: طوابير الرسائل (Message Queues).

ما هي طوابير الرسائل (Message Queues)؟ شرح مبسط

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

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

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

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

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

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

لماذا كانت المعالجة الفورية كابوسًا؟ (The Synchronous Nightmare)

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

  1. تجربة مستخدم سيئة: المستخدم يرى شاشة تحميل تدور وتدور… وقد تصل مدة الانتظار إلى 30 ثانية أو أكثر، فينتهي به الأمر برسالة خطأ (HTTP Timeout). مين فينا بحب يستنى؟
  2. فقدان البيانات والطلبات: إذا حدث أي خلل في الخادم أو انقطع الاتصال أثناء المعالجة، يضيع الطلب إلى الأبد. لا توجد طريقة سهلة لإعادة المحاولة.
  3. صعوبة التوسع (Scaling): إذا أردنا معالجة 100 صورة في نفس الوقت، نحتاج إلى 100 عملية (process/thread) تعمل على خادم الويب. هذا يستنزف موارد الخادم بسرعة كبيرة ويجعله غير قادر على استقبال طلبات جديدة. التوسع هنا يعني زيادة كل شيء معاً، وهو أمر مكلف وغير فعال.

كيف أنقذتنا طوابير الرسائل؟ (The Asynchronous Salvation)

عندما أعدنا تصميم النظام باستخدام طابور رسائل (استخدمنا RabbitMQ في ذلك الوقت)، تغير كل شيء:

  • تحسين فوري لتجربة المستخدم: الآن، عندما يرفع المستخدم صورة، يقوم خادم الويب (المنتِج) بإنشاء رسالة تحتوي على معلومات الطلب (مثل `userId` و `imagePath`) ووضعها في الطابور. تستغرق هذه العملية أجزاء من الثانية. ثم نرسل للمستخدم فوراً رسالة: “تم استلام صورتك بنجاح! سنعلمك عند انتهاء المعالجة.” المستخدم سعيد، وخادم الويب تفرّغ لاستقبال طلبات جديدة.
  • موثوقية وضمان عدم فقدان البيانات: الرسالة الآن محفوظة بأمان في الطابور. إذا تعطل المستهلِك الذي كان يعالجها، تبقى الرسالة في الطابور ليأخذها مستهلِك آخر. وإذا تعطل نظام الطوابير نفسه (وهو أمر نادر إذا تم إعداده بشكل صحيح)، يمكن إعداد خاصية الثبات (Persistence) لحفظ الرسائل على القرص الصلب.
  • مرونة خرافية في التوسع: هذا هو مربط الفرس. هل لدينا ضغط كبير في رفع الصور؟ ببساطة نزيد عدد خوادم الويب (المنتِجين). هل طابور الرسائل يمتلئ بسرعة والمعالجة بطيئة؟ ببساطة نشغّل المزيد من برامج المستهلِك (Consumers) على سيرفرات منفصلة ورخيصة. يمكننا تشغيل 100 مستهلِك إذا أردنا، وكلهم يسحبون من نفس الطابور. أصبحنا نتوسع في الجزء الذي يحتاج للتوسع فقط.

لنطبق الأمر عمليًا: مثال باستخدام RabbitMQ و Node.js

الكلام النظري جميل، لكن خلينا نشوف كيف ممكن نطبق هذا الحكي بكود بسيط. سنستخدم RabbitMQ كـ Message Broker و Node.js لكتابة المنتج والمستهلك.

الخطوة 1: إعداد المنتج (Producer)

هذا الكود يمثل خادم الويب الذي يستقبل الطلب. وظيفته فقط وضع رسالة في طابور اسمه image_processing_queue.


// producer.js
const amqp = require('amqplib');

async function sendImageTask(taskDetails) {
  const queue = 'image_processing_queue';
  let connection;
  try {
    // 1. الاتصال بـ RabbitMQ
    connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();

    // 2. التأكد من وجود الطابور (إذا لم يكن موجوداً، سيتم إنشاؤه)
    // durable: true تعني أن الطابور سيبقى موجوداً حتى لو أعيد تشغيل RabbitMQ
    await channel.assertQueue(queue, { durable: true });

    // 3. إرسال الرسالة إلى الطابور
    // persistent: true تعني أن الرسالة نفسها ستُحفظ على القرص
    channel.sendToQueue(queue, Buffer.from(JSON.stringify(taskDetails)), { persistent: true });
    
    console.log(`[x] تم إرسال المهمة:`, taskDetails);

    // إغلاق الاتصال بعد فترة قصيرة
    setTimeout(() => {
        connection.close();
    }, 500);

  } catch (err) {
    console.error("حدث خطأ:", err);
  }
}

// محاكاة طلب مستخدم جديد
const task = { userId: 'user123', imageUrl: '/path/to/image.jpg', timestamp: new Date() };
sendImageTask(task);

الخطوة 2: إعداد المستهلك (Consumer)

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


// consumer.js
const amqp = require('amqplib');

async function startConsuming() {
  const queue = 'image_processing_queue';
  try {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();

    await channel.assertQueue(queue, { durable: true });

    // prefetch(1) تخبر RabbitMQ ألا يعطي هذا المستهلك أكثر من رسالة واحدة في كل مرة
    // هذا يضمن توزيع الحمل بالتساوي لو كان لدينا عدة مستهلكين
    channel.prefetch(1);

    console.log(`[*] في انتظار الرسائل في طابور ${queue}. للخروج اضغط CTRL+C`);

    channel.consume(queue, (msg) => {
      if (msg !== null) {
        const task = JSON.parse(msg.content.toString());
        console.log(`[.] تم استلام المهمة:`, task);

        // محاكاة عملية معالجة الصورة التي تأخذ وقتاً
        setTimeout(() => {
          console.log(`[✔] تم الانتهاء من معالجة صورة المستخدم: ${task.userId}`);
          // channel.ack(msg) تخبر الطابور أننا انتهينا من معالجة الرسالة بنجاح
          // ويمكنه الآن حذفها بشكل آمن
          channel.ack(msg);
        }, 4000); // تأخذ 4 ثوانٍ
      }
    }, {
      // noAck: false يعني أننا سنرسل تأكيدًا يدويًا (manual acknowledgement)
      // هذا هو الضمان لعدم فقدان الرسالة لو تعطل المستهلك
      noAck: false
    });

  } catch (err) {
    console.error("حدث خطأ:", err);
  }
}

startConsuming();

لاحظ الآن، يمكنك تشغيل producer.js عدة مرات بسرعة، وسترى المهام تتكدس في الطابور. وفي نفس الوقت، يمكنك تشغيل عدة نسخ من consumer.js، وسترى كيف أن كل واحد منهم يسحب مهمة ويعالجها على حدة. لقد بنيت نظاماً موزعاً بسيطاً وقوياً!

نصائح من خبرة “أبو عمر” العملية

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

  • ابدأ بسيطًا: لا تقفز مباشرة إلى أنظمة معقدة مثل Apache Kafka إلا إذا كنت بحاجة لمعالجة تدفقات ضخمة من البيانات (Streaming). في 80% من الحالات، أنظمة مثل RabbitMQ أو حتى Redis Queues تكون أكثر من كافية وسهلة الإدارة.
  • المراقبة هي المفتاح: راقب طول الطابور (Queue Length). إذا كان الطابور ينمو بشكل مستمر، فهذه إشارة واضحة أن المستهلكين لديك لا يلحقون على حجم العمل، وتحتاج لإضافة المزيد منهم.
  • استعد للفشل (Dead Letter Queues): ماذا يحدث إذا فشلت معالجة رسالة ما بشكل متكرر؟ لا تتركها تعود إلى الطابور الرئيسي وتسبب حلقة لا نهائية. قم بإعداد “طابور الرسائل الميتة” (Dead Letter Queue – DLQ). أي رسالة تفشل معالجتها عدة مرات، يتم نقلها إلى هذا الطابور لتحليلها لاحقاً بشكل يدوي.
  • اجعل مهامك “Idempotent”: هذه كلمة معقدة لمفهوم بسيط. يجب أن يكون المستهلك لديك مصمماً بحيث لو استلم نفس الرسالة مرتين، تكون النتيجة النهائية واحدة. مثلاً، لو كانت المهمة “خصم 10 دولارات من رصيد المستخدم”، لا تقم بتنفيذها مباشرة، بل تحقق أولاً “هل تم خصم المبلغ لهذه العملية من قبل؟” هذا يحميك في حال إعادة إرسال الرسائل عن طريق الخطأ.

الخلاصة: من الفوضى إلى النظام 🚀

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

طوابير الرسائل ليست مجرد أداة، بل هي نمط تفكير معماري (Architectural Pattern) أساسي لبناء أي تطبيق حديث يطمح للنمو والتوسع. إنها تفصل بين أجزاء نظامك، تزيد من موثوقيته، وتمنحك مرونة لا تضاهى في التعامل مع الأحمال المتغيرة.

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

أبو عمر

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

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

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

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

آخر المدونات

التكنلوجيا المالية Fintech

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

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

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

كل سيرفر جديد كان قصة رعب: كيف أنقذتني ‘البنية التحتية كشيفرة’ (IaC) من فوضى الإعدادات اليدوية؟

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

26 مارس، 2026 قراءة المزيد
نصائح برمجية

شفرتي كانت هرماً من الشروط المتداخلة: كيف أنقذتني ‘شروط الحماية’ (Guard Clauses) من كابوس الـ if/else؟

هل تعاني من شفرات برمجية معقدة ومليئة بالـ if/else المتداخلة؟ في هذه المقالة، أشاركك تجربتي الشخصية وكيف ساعدتني تقنية "شروط الحماية" (Guard Clauses) في تحويل...

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

خدماتنا كانت متشابكة ككرة صوف: كيف أنقذتني ‘المعمارية الموجهة بالأحداث’ (EDA) من كابوس الاعتماديات الهشة؟

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

26 مارس، 2026 قراءة المزيد
خوارزميات

ذاكرة التخزين المؤقت كانت بلا فائدة: كيف أنقذتني خوارزمية ‘الأقل استخدامًا مؤخرًا’ (LRU) من بطء قاعدة البيانات؟

أشارككم قصة حقيقية عن مشروع كاد أن يفشل بسبب بطء قاعدة البيانات رغم استخدامي للتخزين المؤقت. اكتشفوا كيف كانت خوارزمية بسيطة مثل LRU هي طوق...

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

ألواني الزاهية كانت فخاً: كيف أنقذني ‘تباين الألوان’ من تصميم واجهات كارثية؟

أشارككم قصة حقيقية من بداياتي، عندما كاد حبي للألوان الزاهية أن يدمر مشروعاً كاملاً. اكتشفوا معي كيف تعلمت بالطريقة الصعبة أهمية تباين الألوان (Color Contrast)...

26 مارس، 2026 قراءة المزيد
الشبكات والـ APIs

واجهاتي البرمجية كانت دعوة مفتوحة للمخترقين: كيف أنقذتني ‘بوابة الواجهات البرمجية’ (API Gateway) من كابوس الاستغلال؟

أروي لكم قصتي مع مشروع كاد أن ينهار بسبب ثغرات أمنية في واجهاته البرمجية، وكيف كانت "بوابة الواجهات البرمجية" (API Gateway) هي طوق النجاة. اكتشفوا...

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