أنظمة المهام الخلفية تتولى العمل الذي لا تستطيع معالجات HTTP تنفيذه: إرسال البريد الإلكتروني، معالجة الملفات، مزامنة APIs، وتوليد التقارير. بناء هذا بشكل صحيح من البداية يمنع فئة من حوادث الإنتاج يصعب تصحيحها.
أنظمة معالجة المهام الخلفية تتولى العمل الذي لا تستطيع معالجات HTTP تنفيذه: إرسال البريد الإلكتروني، معالجة الملفات المرفوعة، مزامنة APIs خارجية، توليد التقارير، وتشغيل المهام المجدولة. بناء هذا بشكل صحيح من البداية يمنع فئة من حوادث الإنتاج يصعب تصحيحها ويكلف إصلاحها الكثير عند التوسع.
لماذا تفشل أنظمة المهام الخلفية في الإنتاج؟
أكثر أنماط الفشل شيوعاً في بيئات الإنتاج:
فقدان المهمة عند تعطل العامل. عامل يسحب مهمة من الطابور ويبدأ المعالجة. قبل اكتمال المهمة، يتعطل العامل. بدون ميكانيكية إقرار صحيحة، تضيع المهمة بصمت. المستخدم لا يستلم بريد الفاتورة، ولا أحد يلاحظ حتى يتصل بالدعم.
قطيع الرعد بعد الانقطاع. خدمة خارجية تتعطل 30 دقيقة. تتراكم المهام في الطابور. عند استعادة الخدمة، تُنفَّذ آلاف المهام في وقت واحد، مما يرهق الخدمة المُستعادة ويعيد تشغيل الانقطاع.
تراكم الإخفاقات الصامتة. المهام الفاشلة تُعاد محاولتها، تفشل مجدداً، وتتراكم في طابور الرسائل الميتة الذي لا أحد يراقبه.
انعكاس الأولوية. صادرات خلفية منخفضة الأولوية تحجب رسائل بريد إلكتروني تعاملية عالية الأولوية لأنها تتشارك نفس الطابور ونفس مجموعة العمال.
لماذا Redis لطوابير المهام؟
بالنسبة لمنتجات SaaS في المراحل المبكرة إلى المتوسطة، Redis مع مكتبة مهام مُصممة جيداً يقدم التوازن الصحيح بين البساطة التشغيلية وضمانات الصحة.
Redis يوفر عمليات قائمة ذرية تجعل طوابير المهام الموثوقة ممكنة. أمر BRPOPLPUSH ينقل مهمة ذرياً من طابور الانتظار إلى طابور المعالجة، مما يتيح التسليم مرة واحدة على الأقل حتى عند تعطل العمال.
بالنسبة لـ Go تحديداً، مكتبة asynq هي أكثر نظام مهام قائم على Redis جاهزاً للإنتاج. تتولى الإقرار بشكل صحيح، وتدعم طوابير متعددة بأولويات، وتوفر واجهة مراقبة مدمجة.
تنظيم معالجات المهام في Go
type EmailPayload struct {
TenantID string `json:"tenant_id"`
To string `json:"to"`
Template string `json:"template"`
}
func HandleSendEmail(ctx context.Context, t *asynq.Task) error {
var p EmailPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("unmarshal: %w", err)
}
return emailService.Send(ctx, p.To, p.Template)
}
تصميم الطابور: فصل حسب الأولوية والنوع
تصميم الطابور المسطح حيث تتنافس جميع المهام بالتساوي صحيح فقط إذا كانت جميع المهام لها أهمية متساوية ومتطلبات موارد متساوية. في الواقع، هذا ليس صحيحاً أبداً.
لتطبيق SaaS خلفي، هيكل طابور معقول:
critical(تزامن: 5): رسائل إعادة تعيين كلمة المرور، تأكيدات الدفعdefault(تزامن: 10): البريد الإلكتروني التعاملي، الإشعاراتbulk(تزامن: 3): توليد التقارير، تصدير البياناتscheduled(تزامن: 2): المهام المتكررة، تجميع التحليلات
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: redisAddr},
asynq.Config{
Queues: map[string]int{
"critical": 10,
"default": 5,
"bulk": 2,
"scheduled": 1,
},
Concurrency: 20,
},
)
استراتيجية إعادة المحاولة وطوابير الرسائل الميتة
جدول إعادة المحاولة المعقول:
- المحاولة الأولى: فورية
- المحاولة الثانية: 30 ثانية
- المحاولة الثالثة: 5 دقائق
- المحاولة الرابعة: 30 دقيقة
- المحاولة الخامسة: ساعتان
- بعد 5 محاولات: الانتقال لطابور الرسائل الميتة، تنبيه الفريق
طابور الرسائل الميتة يجب مراقبته. في الإنتاج، نضبط تنبيهاً يُطلَق عند تجاوز 50 مهمة في طابور الرسائل الميتة، ومراجعة أسبوعية لأنماط الإخفاق.
التوكيدية: تصميم المهام لتكون آمنة للإعادة
التسليم مرة واحدة على الأقل يعني أن المهمة قد تُنفَّذ أكثر من مرة. معالجات المهام يجب أن تكون متوكّدة: تشغيل نفس المهمة مرتين يجب أن ينتج نفس النتيجة.
func HandleSendEmail(ctx context.Context, t *asynq.Task) error {
var p EmailPayload
json.Unmarshal(t.Payload(), &p)
sent, err := db.EmailAlreadySent(ctx, p.MessageID)
if err != nil { return err }
if sent { return nil } // متوكد: تخطي النسخة المكررة
if err := emailService.Send(ctx, p); err != nil { return err }
return db.MarkEmailSent(ctx, p.MessageID)
}
المهام المجدولة: استبدال Cron
مهام Cron على خادم واحد هي عنق زجاجة للموثوقية: إذا توقف الخادم، لا تعمل المهام المجدولة. مهام SaaS المجدولة يجب أن تعمل في نفس مجموعة العمال الموزعة كالمهام الفورية:
scheduler := asynq.NewScheduler(
asynq.RedisClientOpt{Addr: redisAddr},
&asynq.SchedulerOpts{},
)
scheduler.Register("0 */6 * * *", asynq.NewTask("sync:external_api", nil))
scheduler.Register("0 2 * * *", asynq.NewTask("report:daily", nil))
المراقبة والرصد
asynq تشحن مع asynqmon، واجهة ويب تُظهر أعماق الطابور، معدلات المعالجة، معدلات الخطأ، ومحتويات طابور الرسائل الميتة.
للتنبيه في الإنتاج، صدّر هذه المقاييس إلى Prometheus:
- عمق الطابور لكل طابور
- معدل المعالجة لكل طابور
- حجم طابور الرسائل الميتة
- وقت معالجة المهمة p99 لكل نوع مهمة
في منصات SaaS للشرق الأوسط تحديداً، المراقبة خلال رمضان مهمة: أنماط حركة المرور تتغير بشكل كبير، وازدحام المساء بعد الإفطار يمكن أن يرفع أعماق الطابور بسرعة.
الدروس من الإنتاج
افصل الطوابير لأولويات مختلفة من اليوم الأول. إعادة بناء فصل الطابور بعد أن بدأ نوع مهمة مجمعة في إجاعة العمليات التعاملية مُكلف تشغيلياً.
جميع معالجات المهام يجب أن تكون متوكّدة. وثّق استراتيجية التوكيدية لكل نوع مهمة، لأن المهندس التالي الذي يعدل المعالج يحتاج للحفاظ عليها.
راقب طابور الرسائل الميتة بفاعلية. التراكم الصامت للمهام الفاشلة يمثل فقدان بيانات غالباً ما يُكتشف بعد أشهر.
هل تبني نظام معالجة مهام لمنصة SaaS؟
Voxire يبني أنظمة معالجة خلفية لمنصات SaaS في لبنان والشرق الأوسط. إذا كنت تصمم نظام طابور مهام أو تصحح مشكلات موثوقية في تطبيق معالجة خلفية قائم، تواصل معنا.
https://voxire.com/get-a-quote/



