تنفيذ مهام خلفية في Python باستخدام Celery وRedis
توجد أعمال لا ينبغي تنفيذها داخل طلب HTTP، مثل إرسال بريد أو إنشاء تقرير كبير أو معالجة صورة. إذا بقي المستخدم منتظراً، ستصبح التجربة بطيئة وقد ينتهي الطلب قبل اكتمال العمل. Celery يسمح بإرسال هذه المهمة إلى عامل مستقل، بينما يمكن استخدام Redis كوسيط رسائل في مثال تعليمي. سنعرف المهمة، ونرسلها، ثم نوضح كيف نراقب النتيجة ولماذا يجب تصميم المهام بحيث يمكن إعادة تنفيذها بأمان.
الفكرة الأساسية
يضع التطبيق رسالة في الوسيط، ثم يلتقطها Worker وينفذ المهمة. لا يعني قبول الرسالة أن العمل انتهى؛ لذلك تحتاج الواجهة إلى معرف مهمة أو حالة يمكن قراءتها لاحقاً. يجب أن تكون المهمة idempotent قدر الإمكان، أي أن إعادة تنفيذها لا تنشئ أثراً مكرراً خطيراً. إذا أرسلت بريداً، خزّن معرف العملية أو استخدم مفتاحاً يمنع التكرار قبل الإرسال.
مثال برمجي عملي
from celery import Celery
import time
app = Celery("tasks", broker="redis://localhost:6379/0")
@app.task(bind=True, max_retries=3)
def build_report(self, report_id):
try:
time.sleep(2)
return {"report_id": report_id, "status": "ready"}
except ConnectionError as error:
raise self.retry(exc=error, countdown=10)
job = build_report.delay(42)
print(job.id)
شرح المثال
ينشئ Celery كائناً يعرف عنوان الوسيط. Decorator task يجعل الدالة قابلة للإرسال إلى Worker بدلاً من تنفيذها في العملية الحالية. تعيد delay كائناً يحمل معرف المهمة، ويمكن استخدامه لعرض الحالة أو النتيجة. المثال يستخدم sleep لمحاكاة عمل بطيء، بينما تستخدم المهمة الحقيقية خدمة أو مكتبة مناسبة. إعادة المحاولة لا تصلح لكل الأخطاء، لذلك يجب التمييز بين فشل مؤقت وبيانات غير صالحة.
ثبت celery وredis وشغل الوسيط، ثم احفظ الكود في tasks.py. شغل Worker بأمر celery -A tasks worker --loglevel=info، ثم نفذ الملف في طرفية أخرى. راقب السجل والنتيجة، وجرب إيقاف الوسيط لمعرفة شكل الفشل. عند ربط المهمة بتطبيق ويب، خزّن الحالة في قاعدة بيانات أو استخدم backend مناسباً بدلاً من إبقاء المتصفح منتظراً.
ابدأ من عقد واضح
قبل فتح المحرر، اكتب ما الذي يدخل إلى البرنامج وما الذي يجب أن يخرج منه. حدد أسماء الحقول وأنواعها والحالات التي تعني نجاحاً أو فشلاً. يساعد هذا العقد على كشف الالتباس مبكراً، ويجعل المثال قابلاً للتجربة من دون الاعتماد على التخمين. إذا تغيرت الفكرة، عدّل العقد أولاً ثم عد إلى التنفيذ.
قسّم العمل إلى خطوة يمكن تشغيلها ومراجعتها. قد تكون الخطوة دالة أو وحدة أو مساراً، بحسب التقنية. لا تخلط قراءة البيانات والتحقق منها وحفظها في كتلة واحدة إذا كان فصلها سيجعل الخطأ أوضح. وفي الوقت نفسه لا تنشئ طبقات كثيرة قبل أن تعرف ما الذي تحتاج إلى فصله.
تجارب ينبغي ألا تهملها
اختبر مدخلاً صحيحاً، ومدخلاً ناقصاً، وقيمة من نوع غير متوقع، ثم اختبر الحالة التي لا تعيد أي نتيجة. في المشاريع التي تتعامل مع شبكة أو ملفات، جرّب انقطاع المصدر وعودة استجابة بطيئة. اكتب النتيجة المتوقعة قبل التنفيذ، لأن المقارنة بين التوقع والواقع تساعدك على اكتشاف افتراضات مخفية.
- اجعل المثال قابلاً للتشغيل بعد إعداد قصير ومذكور.
- افصل الخطأ الذي يمكن للمستخدم إصلاحه عن خطأ الخادم.
- لا تكرر القاعدة نفسها في أكثر من دالة إذا كان يمكن وضعها في مكان واحد.
- راجع أثر التغيير على المستهلكين الحاليين للواجهة أو الوحدة.
ما الذي يجعل الحل عملياً؟
الحل العملي ليس الأطول، بل الذي يمكن قراءته وتعديله بعد انتهاء التجربة. اختر أسماء تشرح الغرض، واكتب تعليقات تشرح السبب لا ما يفعله السطر حرفياً. ضع الإعدادات المتغيرة في بيئة التشغيل، وحافظ على سجل واضح للتغييرات. عندما يعمل المثال، اطلب من شخص آخر أن يشرح خطواته؛ الأسئلة التي يطرحها ستكشف نقاطاً تحتاج إلى تبسيط.
خطوة ما بعد المثال
بعد تشغيل الكود، أضف حالة فشل واختباراً لها، ثم غيّر جزءاً واحداً في كل مرة. هذا الأسلوب يمنعك من فقدان مصدر المشكلة ويجعلك ترى أثر كل قرار. إذا احتجت إلى مكتبة جديدة، اقرأ حدودها قبل دمجها، وتأكد من أن فائدتها أكبر من كلفة إضافتها إلى المشروع.
أخطاء ينبغي تجنبها
لا ترسل كائناً ضخماً إلى الوسيط، ولا تعتمد على متغير عام يتغير بين عمليات العمال. لا تجعل المهام غير محدودة الزمن، ولا تستخدم إعادة المحاولة لإخفاء خطأ برمجي ثابت. راقب عدد الرسائل المتراكمة، وحدد سياسات للمهام التي انتهت أو فشلت مرات كثيرة.
خطوة تالية مناسبة
أضف حالات pending وrunning وfailed، ثم أنشئ مساراً يعيد الحالة للواجهة. جرّب جدولة مهمة دورية، لكن افصل الجدولة عن منطق المهمة حتى يمكن تشغيلها يدوياً أثناء الاختبار.
تنظيم المهام الخلفية
المهمة الخلفية تحتاج إلى تعريف واضح لحالتها: معلقة، قيد التنفيذ، مكتملة، أو فاشلة. خزّن عدد المحاولات وسبب الفشل الأخير، وضع سياسة تمنع إعادة تنفيذ عملية غير آمنة من دون معرف idempotency. افصل وقت انتظار المستخدم عن وقت معالجة المهمة، وأعد له معرفاً يستطيع بواسطته الاستعلام عن الحالة. عند تشغيل أكثر من عامل، تأكد من أن المهمة لا تُسحب مرتين في اللحظة نفسها. كما يجب أن تملك طريقة لإيقاف المهام القديمة وتنظيف السجلات، لأن الطابور الذي لا ينظف نفسه يتحول إلى مصدر ضغط جديد.
اكتب ملاحظة قصيرة عن القرار الذي اتخذته أثناء التجربة، وسجل ما قسته أو اختبرته بدلاً من الاكتفاء بانطباع عام. قارن النسخة البسيطة بالنسخة التي أضفت إليها هذه الخطوة، ثم اسأل هل تحسن الوضوح أو الأداء أو سهولة الصيانة. إذا لم يظهر أثر عملي، ارجع إلى التصميم الأبسط. هذه المراجعة تمنع تحول المثال التعليمي إلى تعقيد ثابت لا يخدم المستخدم.
خطة تطبيق عملية
بعد فهم موضوع «تنفيذ مهام خلفية في Python باستخدام Celery وRedis»، لا تنقل المثال إلى مشروع كبير دفعة واحدة. أنشئ مجلداً صغيراً، وثبت نسخة الأدوات التي ستستخدمها، ثم اكتب حالة نجاح واحدة يمكن تشغيلها من البداية إلى النهاية. احتفظ بالمدخل الذي استخدمته والنتيجة التي توقعتها، لأن ذلك يمنحك نقطة مقارنة عندما تغير الكود. إذا كان الموضوع يعتمد على خدمة أو قاعدة بيانات، جهز بيانات تجريبية لا تحمل معلومات حقيقية، واكتب طريقة تنظيفها بعد انتهاء الاختبار.
انتقل بعد ذلك إلى الحالات التي تسبب الالتباس. ماذا يحدث عندما تكون القيمة فارغة؟ كيف يتصرف البرنامج عند وصول نوع غير متوقع؟ هل تعود رسالة مفيدة إذا توقفت الخدمة الخارجية أو لم يجد التطبيق السجل المطلوب؟ اكتب إجابة لكل سؤال في اختبار أو ملاحظة قصيرة. لا تحاول معالجة كل احتمال في سطر واحد؛ فصل المسارات يجعل التصحيح أسهل ويمنع إخفاء المشكلة خلف استثناء عام.
من المفيد أن تقيس قبل التحسين وبعده. قد يكون القياس زمناً أو حجماً أو عدد استدعاءات أو نسبة أخطاء، بحسب طبيعة الموضوع. لا تعتمد على الانطباع وحده، ولا تقارن تشغيلين مختلفين من دون تثبيت الظروف قدر الإمكان. إذا لم يتحسن المؤشر، تراجع عن التغيير وابحث عن السبب بدلاً من إضافة طبقة أخرى. وبعد أن تستقر النتيجة، اكتب README قصيراً يوضح أمر التشغيل، المدخلات، المخرجات، وأهم قرار اتخذته أثناء البناء.
أخيراً، راجع حدود المثال. الكود التعليمي يشرح المفهوم، لكنه قد يحتاج في الإنتاج إلى صلاحيات، ومراقبة، واختبارات، وإدارة أسرار، وسياسة للتحديث. أضف ما يبرره الاستخدام الفعلي فقط. بهذه الطريقة تتعلم التقنية من دون أن تخلط بين نموذج صغير ونظام جاهز للمستخدمين.
مراقبة المهمة
لا تكتفِ برسالة تقول إن العامل بدأ العمل. سجّل معرف المهمة ووقت البدء والنهاية وعدد المحاولات، واجعل حالة الفشل قابلة للبحث. عند وجود أكثر من عامل، اختبر توقف أحدها ثم عودته، وتأكد من أن الرسالة لا تضيع أو تنفذ مرتين بلا قصد. هذه التفاصيل تجعل الطابور أداة يمكن الوثوق بها بدلاً من صندوق أسود يصعب تفسيره.
الخلاصة
أضف حالات pending وrunning وfailed، ثم أنشئ مساراً يعيد الحالة للواجهة. جرّب جدولة مهمة دورية، لكن افصل الجدولة عن منطق المهمة حتى يمكن تشغيلها يدوياً أثناء الاختبار. يوضح هذا الموضوع كيف تتحول الفكرة النظرية إلى خطوات يمكن تشغيلها وفحصها.
ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.