يوم الجمعة اللي كان رح يصير “أسود” على راسي!
خلوني أرجع بالذاكرة كم سنة لورا. كنت وقتها فخور جدًا بتطبيق متجر إلكتروني صغير بنيته لواحد من العملاء. كل شي كان ماشي زي الحلاوة، التطبيق سريع، وتجربة المستخدم ممتازة، والعميل مبسوط. قرب موسم الجمعة السوداء (Black Friday)، وقررنا نعمل عروض “ما صارت ولا استوت”. كنت متوقع زيادة في عدد المستخدمين، بس يا جماعة اللي صار كان فوق كل التوقعات.
في الدقائق الأولى من إطلاق العروض، بدأت توصلني التنبيهات على الموبايل. في الأول فرحت، “ما شاء الله، إقبال كبير!”. بس الفرحة ما دامت طويل. فجأة، بدأت التنبيهات تتغير: “Server CPU at 99%”, “Database connection timeout”, “504 Gateway Timeout”. فتحت لوحة المراقبة (Dashboard) وشفت الخطوط الحمرا طالعة للسما. التطبيق صار بطيء جدًا، لدرجة إنه شبه متوقف. المستخدمون بضغطوا على زر “إتمام الشراء” وما بصير إشي، والطلبات بتضيع، ورسائل التأكيد على الإيميل ما بتوصل.
حسيت العرق البارد نازل على ظهري. هذا مش بس فشل تقني، هاي سمعة العميل وتجارتهم اللي على المحك. كنت قاعد أتنقل بين شاشات الكود وسجلات الخادم (Server Logs) زي المجنون، بحاول أفهم وين “الخنقة”. المشكلة ما كانت في الكود نفسه، المشكلة كانت في “الطريقة” اللي الكود بعالج فيها الطلبات.
كل طلب شراء كان لازم يعمل سلسلة من العمليات ورا بعض: التحقق من الدفع، خصم المنتج من المخزون، تسجيل الطلب في قاعدة البيانات، إرسال إيميل تأكيد للمشتري، إرسال إشعار لفريق التجهيز… كل هذا والمستخدم قاعد بستنى الصفحة تحمل! مع مئات الطلبات في الدقيقة، النظام ببساطة “انخنق” وما قدر يلحق. وهون كانت اللحظة اللي قلت فيها لنفسي: “خلص، لازم نشتغل صح. لازم نستخدم طوابير الرسائل”.
المشكلة الحقيقية: المعالجة المتزامنة (Synchronous Processing)
قبل ما نحكي عن الحل، خلونا نفهم أصل المشكلة. معظم التطبيقات في بدايتها بتشتغل بطريقة اسمها “المعالجة المتزامنة”. تخيل إنك رحت على مطعم، وطلبت وجبة. في عالم المعالجة المتزامنة، الموظف اللي أخذ طلبك (الكاشير) هو نفسه اللي بروح على المطبخ يطبخ، وبعدين يغلف الأكل، وبعدين يرجع يحاسبك. خلال كل هالوقت، أنت وكل الناس اللي وراك في الطابور لازم تستنوا، ما حدا بقدر يعمل إشي.
هذا بالضبط اللي كان يصير في تطبيقي:
- المستخدم يضغط “شراء”.
- الخادم يستقبل الطلب ويبدأ بالعمليات (الدفع، المخزون، قاعدة البيانات، الإيميل…).
- المستخدم ينتظر… وينتظر… وينتظر.
- فقط بعد انتهاء كل هذه العمليات، يستقبل المستخدم رسالة “تم الطلب بنجاح”.
لما يكون عندك 10 مستخدمين في الساعة، هالطريقة مقبولة. بس لما يصير عندك 1000 مستخدم في الدقيقة، النظام بصير عنده طابور طويل من المهام اللي لازم يخلصها “بالترتيب”، وهذا يؤدي إلى انهيار كل شيء.
الحل السحري: طوابير الرسائل والمعالجة غير المتزامنة (Asynchronous Processing)
شو رأيك لو المطعم غير طريقته؟ الكاشير بس بوخذ منك الطلب وبعطيك رقم، وبقلك “طلبك قيد التجهيز، ارتاح واحنا بناديك لما يجهز”. الكاشير فورًا بكون جاهز ياخذ طلب من الزبون اللي بعدك. في الخلف، في فريق كامل من الطباخين (العمّال) كل واحد فيهم بوخذ طلب من الطابور وبجهزه. هيك المطعم بقدر يخدم عدد أكبر من الزبائن بسرعة وكفاءة.
هذا بالضبط هو مبدأ طوابير الرسائل (Message Queues). هي ببساطة “وسيط” أو “صندوق بريد” رقمي بنحط فيه المهام (الرسائل) بدل ما ننفذها فورًا.
مكونات نظام طوابير الرسائل
النظام عادة بتكون من 3 أجزاء رئيسية:
- المنتِج (Producer): هو الجزء من تطبيقك اللي بنشئ المهمة. في قصتنا، هو الكود اللي بتنفذ لما المستخدم يضغط “شراء”. مهمته بسيطة: يكتب رسالة فيها كل تفاصيل الطلب (زي رقم الطلب، إيميل المستخدم، المنتجات) ويحطها في الطابور.
- الطابور (Queue): هو المكان اللي بتتخزن فيه الرسائل بالترتيب (First-In, First-Out). زي طابور البنك بالضبط، اللي بيجي أول بنخدم أول.
- المستهلِك (Consumer/Worker): هو برنامج منفصل تمامًا عن تطبيقك الرئيسي. كل وظيفته في الحياة إنه يضل يراقب الطابور. كل ما يلاقي رسالة جديدة، بوخذها وببدأ ينفذ المهام الثقيلة اللي فيها (معالجة الدفع، إرسال الإيميل، تحديث المخزون، إلخ).
بهذه الطريقة، تطبيق الويب الرئيسي (المنتِج) بصير سريع جدًا. كل ما عليه فعله هو وضع رسالة في الطابور، وهذا بياخذ أجزاء من الثانية، وبعدها مباشرة برجع للمستخدم رسالة “شكرًا لك! طلبك رقم #123 قيد المعالجة وستصلك رسالة تأكيد قريبًا”. تجربة المستخدم صارت ممتازة، والخادم الرئيسي بطل عليه ضغط.
مثال عملي بالكود: من النظرية إلى التطبيق
الحكي حلو، بس خلونا نشوف كيف ممكن نطبق هالكلام. رح أستخدم مثال بسيط بلغة Python مع مكتبة pika للتواصل مع نظام طوابير رسائل مشهور اسمه RabbitMQ.
1. كود المُنتِج (Producer) – داخل تطبيق الويب
هذا الكود يتم استدعاؤه عند إتمام عملية الشراء. لاحظ بساطته، هو فقط يرسل رسالة.
import pika
import json
# الاتصال بـ RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# نتأكد من وجود الطابور، إذا مش موجود بننشئه
channel.queue_declare(queue='orders_queue', durable=True) # durable=True عشان الرسائل ما تضيع لو السيرفر طفى
def process_new_order(order_details):
# order_details هو قاموس (dictionary) يحتوي على معلومات الطلب
# مثلا: {'order_id': 123, 'user_email': 'user@example.com', 'amount': 99.9}
# إرسال الرسالة إلى الطابور
channel.basic_publish(
exchange='',
routing_key='orders_queue',
body=json.dumps(order_details), # نحول البيانات لـ JSON string
properties=pika.BasicProperties(
delivery_mode=2, # نجعل الرسالة دائمة (persistent)
))
print(f" [x] تم إرسال الطلب رقم {order_details['order_id']} إلى الطابور")
# لا ننسى إغلاق الاتصال
connection.close()
# مثال على الاستخدام
# هذا ما يحدث في خلفية تطبيق الويب بعد ضغط المستخدم على زر الشراء
new_order = {'order_id': 456, 'user_email': 'another@example.com', 'items': ['item1', 'item2']}
process_new_order(new_order)
2. كود المستهلِك (Consumer) – برنامج منفصل يعمل باستمرار
هذا برنامج (أو عدة نسخ منه) يعمل على خادم آخر أو في الخلفية. وظيفته هي الاستماع للطابور ومعالجة الرسائل.
import pika
import json
import time
# الاتصال بـ RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='orders_queue', durable=True)
print(' [*] في انتظار الرسائل. للخروج اضغط CTRL+C')
# هذه هي الدالة التي ستقوم بالعمل الحقيقي
def perform_heavy_tasks(order_details):
print(f" [x] جاري معالجة الطلب رقم: {order_details['order_id']}")
# 1. تحديث قاعدة البيانات
time.sleep(2) # محاكاة لعملية تأخذ وقتاً
print(" - تم تحديث قاعدة البيانات.")
# 2. إرسال إيميل تأكيد
time.sleep(3) # محاكاة لعملية إرسال الإيميل
print(f" - تم إرسال إيميل إلى {order_details['user_email']}.")
# 3. إرسال إشعار لفريق التجهيز
time.sleep(1)
print(" - تم إرسال إشعار للمخزن.")
print(f" [x] اكتملت معالجة الطلب رقم: {order_details['order_id']}")
# هذه دالة الـ callback التي يتم استدعاؤها عند وصول رسالة جديدة
def callback(ch, method, properties, body):
order_details = json.loads(body)
perform_heavy_tasks(order_details)
# نرسل تأكيد (acknowledgment) لـ RabbitMQ بأن الرسالة تمت معالجتها بنجاح
# هذا يضمن أنه لو تعطل المستهلك في منتصف العمل، الرسالة لن تُحذف من الطابور
ch.basic_ack(delivery_tag=method.delivery_tag)
# نطلب من RabbitMQ أن لا يرسل لنا رسالة جديدة إلا بعد أن ننتهي من الحالية
channel.basic_qos(prefetch_count=1)
# نبدأ الاستماع للطابور
channel.basic_consume(queue='orders_queue', on_message_callback=callback)
channel.start_consuming()
لاحظ كيف أننا فصلنا المهمة السريعة (استقبال الطلب) عن المهام البطيئة (معالجة الطلب). الآن، لو جاء 1000 طلب في دقيقة، تطبيق الويب سيقوم بإضافة 1000 رسالة إلى الطابور بسرعة البرق، ثم يمكننا تشغيل 10 أو 20 أو حتى 100 نسخة من برنامج “المستهلِك” ليقوموا بمعالجة هذه الطلبات على التوازي. هذه هي قوة التوسع (Scalability)!
نصائح من خبرة أبو عمر
الشغلة مش سحر، وفي كم نقطة لازم تنتبه إلها لما تطبق هالنظام:
- التعامل مع الفشل (Handling Failures): ماذا لو فشلت معالجة رسالة (مثلاً، خدمة إرسال الإيميل كانت متعطلة)؟ يجب أن تبرمج “المستهلِك” ليعيد المحاولة عدة مرات. وإذا استمر الفشل، يجب نقل الرسالة إلى طابور خاص يسمى “طابور الرسائل الميتة” (Dead Letter Queue – DLQ) لمراجعتها يدويًا لاحقًا. لا تدع رسالة فاشلة توقف معالجة كل الطابور.
- التكرار الآمن (Idempotency): بسبب مشاكل في الشبكة أو إعادة المحاولة، قد تتم معالجة نفس الرسالة مرتين. يجب أن يكون الكود الخاص بك “Idempotent”، بمعنى أن تنفيذ العملية مرتين يعطي نفس نتيجة تنفيذها مرة واحدة. مثلاً، لا تخصم المبلغ من المستخدم مرتين! تحقق دائمًا إذا كان الطلب قد تم معالجته مسبقًا عن طريق حالته في قاعدة البيانات.
- المراقبة ثم المراقبة (Monitoring): لازم يكون عندك لوحة مراقبة تظهر لك عدد الرسائل في الطابور، سرعة المعالجة، وعدد “المستهلكين” النشطين. هذا يساعدك تعرف متى تحتاج لزيادة عدد “المستهلكين” في أوقات الذروة.
- اختر الأداة المناسبة: هناك العديد من أنظمة طوابير الرسائل. RabbitMQ ممتاز وقوي ويمكنك استضافته بنفسك. Amazon SQS هو خدمة سحابية رائعة، سهلة الإعداد وتتوسع تلقائيًا. Kafka أكثر تعقيدًا ولكنه مناسب للتعامل مع كميات هائلة من البيانات (Streaming). ابدأ بالبسيط الذي يحل مشكلتك.
الخلاصة: لا تبنِ قلعة على أساس من رمل
في عالم تطوير البرمجيات، من السهل أن نركز على الميزات الجديدة ونهمل الأساسات. قصة الجمعة السوداء علمتني درسًا قاسيًا: الأداء العالي والقابلية للتوسع ليست رفاهية، بل هي جزء أساسي من بناء تطبيق ناجح وموثوق. طوابير الرسائل هي واحدة من أهم الأدوات في صندوق عدة أي مطور يريد بناء أنظمة قادرة على تحمل ضغط العالم الحقيقي.
لا تنتظر حتى تغرق سفينتك لتبدأ في بناء قوارب النجاة. ابدأ من اليوم، انظر إلى تطبيقك، وحدد العمليات البطيئة التي تحدث في الخلفية. قد تكون إرسال إيميلات، أو معالجة صور، أو إنشاء تقارير. حولها إلى عملية غير متزامنة باستخدام طابور رسائل. صدقني، ستشكر نفسك في “الجمعة السوداء” القادمة. 😉