يا جماعة الخير، السلام عليكم ورحمة الله. معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال مع فريقي على مشروع لمنصة تجارة إلكترونية كبيرة. كل شي كان ماشي تمام، والموقع كان سريع ومستقر. بعد فترة، طلب منا العميل إضافة ميزة جديدة: إنشاء تقارير وفواتير PDF مخصصة لكل طلبية. الفاتورة كانت معقدة شوي، لازم تجيب بيانات من أكتر من جدول في قاعدة البيانات، وتضيف صور المنتجات، وشعار الشركة، وتحسب حسابات ضريبية… شغل بده وقت.
في بيئة التطوير عنا، كل شي كان “عال العال”. نضغط على زر “إنشاء الفاتورة”، وبعد ثانيتين ثلاثة، تطلع الفاتورة. لكن المصيبة صارت يوم الإطلاق. أول ما بدأ المستخدمون الفعليون يضغطوا على الزر، بدأت الشكاوى توصل. “الموقع بعلّق”، “الطلب بنتظر للأبد وما بصير إشي”، “الصفحة بتعطيني خطأ Timeout”.
كانت كارثة بكل معنى الكلمة. الخادم (السيرفر) تبعنا كان يوصل لأقصى طاقته ويصير يرفض الطلبات الجديدة لأنه مشغول بإنشاء هالفواتير الثقيلة. المستخدم يضغط الزر، والمتصفح يظل يستنى… ويستنى… ويستنى… لحد ما يزهق ويقفل الصفحة أو يوصل لوقت الاستجابة الأقصى (Timeout). كان موقف محرج جدًا، والعميل ما كان مبسوط بالمرة. قعدنا نفكر بالحل، وهنا كانت بداية قصتنا مع بطل مقالتنا اليوم: طوابير الرسائل (Message Queues).
شو قصة طوابير الرسائل هاي؟ (المفهوم ببساطة)
قبل ما ندخل في التفاصيل التقنية، خليني أبسطلك الفكرة بمثال من حياتنا. تخيل إنك رحت على مكتب بريد عشان ترسل طرد. هل بتوقف عند الشباك وبتستنى ساعي البريد ياخذ الطرد تبعك ويسافر ويوصله لبيت المستلم، وبعدين يرجع ويقلك “وصل”؟ طبعًا لأ.
اللي بصير هو إنك بتسلم الطرد للموظف (هذا اسمه المنتج – Producer)، والموظف بحطه في مكان مخصص مع باقي الطرود (هذا هو الطابور – Queue). هيك مهمتك انتهت، وبتروح تكمل يومك. بعدين، بيجي ساعي البريد (هذا اسمه المستهلك – Consumer) وبياخذ الطرود من الطابور وبيبدأ يوزعها. أنت كمرسل ما بتعرف متى بالضبط رح يوصل الطرد، بس متأكد إنه انحط في النظام وراح يتم توصيله.
طوابير الرسائل في البرمجة هي نفس المبدأ تمامًا. هي عبارة عن وسيط بين أجزاء مختلفة من نظامك. بدل ما جزء من النظام (مثل خادم الويب) يقوم بمهمة ثقيلة بنفسه وينتظرها تخلص، هو بس “بيرسل رسالة” فيها تفاصيل المهمة إلى طابور، وبيرجع فورًا يرد على المستخدم “تم استلام طلبك وجاري معالجته”. وفي الخلفية، في جزء آخر من النظام (عامل أو Worker) بيسحب هاي الرسائل من الطابور وبينفذ المهام وحدة ورا التانية.
ليش كنا بحاجة هالحل؟ (تشخيص المشكلة)
المشكلة اللي واجهناها في قصة الفواتير كانت الها ثلاث أبعاد رئيسية، وطوابير الرسائل حلتها كلها.
المشكلة الأولى: تجربة المستخدم السيئة والاستجابات البطيئة
كان المستخدم يضغط على الزر، وخادم الويب يبدأ مباشرة في عملية إنشاء الـ PDF. هاي العملية كانت تاخذ من 20 إلى 40 ثانية. طوال هاي الفترة، المتصفح تبع المستخدم “معلّق” ومنتظر رد من الخادم. أغلب المتصفحات والخوادم عندها حد أقصى للانتظار (عادة 30 ثانية)، فكانت كثير من الطلبات تفشل.
الحل مع الطابور: الآن، لما المستخدم يضغط الزر، خادم الويب ما بيعمل أي شغل ثقيل. هو بس بيكتب رسالة بسيطة مثل {"action": "generate_invoice", "order_id": 123} وبيحطها في الطابور. هاي العملية بتاخذ أجزاء من الثانية. بعدها مباشرة، الخادم بيرد على المستخدم برسالة لطيفة: “طلبك قيد المعالجة، رح نرسلك إيميل بالفاتورة بس تجهز”. المستخدم مبسوط لأنه أخذ رد فوري، والنظام بيكمل شغله في الخلفية بهدوء.
المشكلة الثانية: الاقتران الشديد (Tight Coupling)
كان خادم الويب مسؤول مباشرة عن منطق إنشاء الفواتير. هذا يعني لو صار أي خطأ أثناء إنشاء الفاتورة (مثلاً، مشكلة في مكتبة الـ PDF)، كان الطلب كله يفشل ويظهر خطأ 500 للمستخدم. ولو حبينا نحدّث خدمة إنشاء الفواتير، كان لازم نوقف خادم الويب كله ونعمل تحديث، وهذا بيعني توقف الخدمة.
الحل مع الطابور: صار عنا فصل كامل (Decoupling) بين خدمة الويب وخدمة إنشاء الفواتير. خادم الويب ما بيعرف أي شي عن كيفية إنشاء الفواتير، هو بس بيعرف يرسل رسالة للطابور. وخدمة الفواتير (العامل/Consumer) ما بتعرف أي شي عن خادم الويب، هي بس بتعرف تقرأ من الطابور. لو خدمة الفواتير توقفت أو فشلت، الرسائل بتضل محفوظة في الطابور لحد ما ترجع الخدمة تشتغل تاني. النظام صار أكثر مرونة وقوة.
المشكلة الثالثة: صعوبة التوسع (Scalability)
لما زاد الضغط وصار عنا 100 مستخدم بيطلبوا فواتير بنفس الوقت، خادمنا المسكين ما قدر يتحمل. عشان نحل المشكلة بالطريقة القديمة، كنا رح نضطر نجيب خوادم ويب أقوى وأغلى (وهذا اسمه توسع رأسي – Vertical Scaling). لكن ماذا لو زاد الضغط 10 أضعاف؟
الحل مع الطابور: بما إنه الشغل الثقيل صار يتم عن طريق “عمال” منفصلين، صرنا نقدر نتوسع بسهولة. هل الطابور بدأ يتملى بالرسائل بسرعة؟ بسيطة، بنشغل كمان “عامل” أو اثنين أو عشرة على خوادم رخيصة. هدول العمال كلهم بيسحبوا من نفس الطابور وبيوزعوا الشغل بينهم. هذا اسمه توسع أفقي (Horizontal Scaling)، وهو أرخص وأكثر فعالية بكثير.
مثال عملي: خلينا نكتب شوية كود
الحكي النظري حلو، بس “الزبدة” في التطبيق. خلينا نشوف مثال بسيط باستخدام لغة Python ومكتبة Celery الشهيرة، مع RabbitMQ كطابور رسائل.
تخيل عنا تطبيق ويب بسيط معمول بـ Flask. هذا هو الجزء اللي بيستقبل الطلب من المستخدم (المنتج – Producer).
1. المنتج (تطبيق الويب)
هذا الكود هو اللي بيستقبل طلب المستخدم، وبيرسل مهمة للطابور بدون ما يعمل الشغل الثقيل بنفسه.
# app.py - تطبيق فلاسك
from flask import Flask
from tasks import generate_invoice_task # نستورد المهمة من ملف آخر
app = Flask(__name__)
@app.route('/invoice/<order_id>')
def create_invoice(order_id):
# لا تقم بالعمل الثقيل هنا!
# فقط أرسل رسالة إلى الطابور باستخدام .delay()
print(f"تم استلام طلب لإنشاء فاتورة للطلب رقم: {order_id}")
generate_invoice_task.delay(order_id)
# أرجع استجابة فورية للمستخدم
return "طلبك قيد المعالجة! سنرسل لك الفاتورة عبر البريد الإلكتروني قريباً."
if __name__ == '__main__':
app.run(debug=True)
2. المستهلك (العامل أو الـ Worker)
هذا هو الكود اللي بيشتغل في الخلفية على خادم ثاني (أو في عملية منفصلة). هو بيضل يراقب الطابور، وأول ما توصل رسالة جديدة، بيسحبها وبينفذ المهمة.
# tasks.py - ملف تعريف المهام
from celery import Celery
import time
# إعداد Celery لاستخدام RabbitMQ كوسيط للرسائل
# 'tasks' هو اسم التطبيق الحالي
# 'broker' هو عنوان الطابور
app = Celery('tasks', broker='amqp://guest:guest@localhost:5672//')
@app.task
def generate_invoice_task(order_id):
"""
هذه هي المهمة الثقيلة التي ستعمل في الخلفية.
"""
print(f"بدأنا العمل على الفاتورة للطلب رقم: {order_id}")
# محاكاة لعملية طويلة ومعقدة (جلب بيانات، إنشاء PDF، إلخ)
try:
# هنا تضع منطق العمل الحقيقي
time.sleep(20) # نتظاهر أن العملية تأخذ 20 ثانية
print(f"✅ تم إنشاء الفاتورة للطلب رقم: {order_id} بنجاح!")
# في العادة، هنا تقوم بإرسال إيميل أو تحديث قاعدة البيانات
return {"status": "Success", "order_id": order_id}
except Exception as e:
print(f"❌ فشل في إنشاء الفاتورة للطلب رقم: {order_id}. الخطأ: {e}")
# يمكنك هنا إعادة محاولة المهمة أو إرسالها إلى طابور آخر للمراجعة
raise
لتشغيل هذا النظام، ستقوم بتشغيل تطبيق Flask، ثم في نافذة طرفية (terminal) أخرى، ستقوم بتشغيل العامل (Worker) الخاص بـ Celery بالأمر التالي:
celery -A tasks worker --loglevel=info
الآن، كلما قمت بزيارة الرابط /invoice/123 في متصفحك، سترى الاستجابة الفورية، وفي الطرفية الأخرى، سترى العامل يبدأ في معالجة المهمة بعد لحظات.
نصائح من خبرة أبو عمر
بعد سنوات من الشغل مع طوابير الرسائل، تعلمت كم درس “على جلدي”. اسمحولي أشاركم أهمها:
- اجعل مهامك قابلة للتكرار بأمان (Idempotent): أحيانًا، قد يفشل العامل بعد تنفيذ المهمة ولكن قبل أن يخبر الطابور بأنه انتهى. في هذه الحالة، قد يتم إرسال نفس الرسالة إلى عامل آخر. يجب أن يكون الكود تبعك قادرًا على التعامل مع هذا. مثلاً، قبل خصم مبلغ من حساب بنكي، تحقق أولاً: “هل تم خصم هذا المبلغ لهذه العملية من قبل؟”.
- راقب طابورك: أهم مؤشر على صحة نظامك هو طول الطابور. إذا كان الطابور ينمو بشكل مستمر ولا ينقص، فهذا يعني أن “العمال” لا يلحقون على الشغل، وتحتاج إما لزيادة عددهم أو تحسين أداء المهام نفسها. استخدم أدوات مراقبة مثل لوحة تحكم RabbitMQ أو أدوات مثل Datadog.
- استخدم طابور الرسائل الميتة (Dead Letter Queue): ماذا تفعل برسالة تفشل في كل مرة تحاول معالجتها؟ إذا تركتها، ستظل تعاد معالجتها وتفشل، وتعيق باقي الرسائل. الحل هو إعداد “طابور الرسائل الميتة”. بعد عدد معين من المحاولات الفاشلة، يتم نقل الرسالة تلقائيًا إلى هذا الطابور الخاص لمراجعتها يدويًا لاحقًا.
- اختر الأداة المناسبة: هناك العديد من أنظمة طوابير الرسائل، وكل منها له نقاط قوة.
- RabbitMQ: ممتاز للمهام التقليدية، مرن جدًا، ويوفر أنماط توجيه معقدة. هو خياري المفضل في أغلب الحالات.
- Amazon SQS: حل مُدار بالكامل من أمازون. سهل جدًا إذا كنت تستخدم بيئة AWS بالفعل.
- Redis: يمكن استخدامه كطابور بسيط جدًا. سريع وخفيف، لكنه يفتقر لبعض الميزات المتقدمة مثل ضمان وصول الرسائل في حال تعطل الخادم (إلا مع إعدادات معينة).
- Apache Kafka: هو أكثر من مجرد طابور، هو منصة تدفق بيانات (streaming platform). مناسب جدًا لكميات هائلة من البيانات التي تحتاج لمعالجة في الوقت الفعلي، مثل تتبع سلوك المستخدمين أو سجلات (logs).
الخلاصة
طوابير الرسائل ليست مجرد أداة تقنية، بل هي نقلة نوعية في طريقة تفكيرنا في بناء الأنظمة. الانتقال من المعالجة المتزامنة (Synchronous) إلى المعالجة غير المتزامنة (Asynchronous) يمنحك تطبيقات أسرع للمستخدم، وأكثر قوة ومرونة في مواجهة الأخطاء، وقابلة للتوسع بشكل شبه لا نهائي.
في قصتنا، بعد تطبيق نظام طوابير الرسائل، تحولت تجربة المستخدم من الانتظار المحبط إلى استجابة فورية. وبدلًا من الخوف من زيادة الضغط، صرنا نرحب به لأننا كنا نعرف أن كل ما علينا فعله هو تشغيل “عامل” إضافي بكبسة زر.
نصيحتي الأخيرة لك: إذا كان لديك أي عملية في تطبيقك تأخذ أكثر من نصف ثانية، فكر جديًا في نقلها إلى الخلفية باستخدام طابور رسائل. ستشكرني لاحقًا. 👍
الله يرضى عليكم ويوفقكم في مشاريعكم.