قوائم المهام في Go (Golang): نظرة عامة ومقارنة الأدوات
في تطوير الويب الحديث وإنشاء الخدمات عالية التحميل بلغة Go (Golang)، تنشأ دائمًا الحاجة تقريبًا لتنفيذ المهام بشكل غير متزامن، خارج التدفق الرئيسي لمعالجة طلبات HTTP. إرسال إشعارات البريد الإلكتروني، إنشاء تقارير PDF، معالجة الصور، التفاعل مع واجهات برمجة التطبيقات الخارجية - كل هذا يتطلب نقلها إلى عمليات خلفية. وهنا تأتي قوائم المهام (task queues) لإنقاذ الموقف.
قائمة المهام هي آلية تسمح بتأجيل تنفيذ عملية ما، وضمان إعادة معالجتها في حالة حدوث عطل، وتحقيق التوازن في الحمل بين عدة عمال. في نظام Go البيئي، توجد العديد من المكتبات الناضجة والنشطة في التطوير لحل هذه المشكلة. في هذه المقالة، سنقوم بتحليل ثلاثة من أشهر الأساليب بالتفصيل: Asynq، Machinery والعمل المباشر مع Redis Streams.
1. Asynq: البساطة والقوة المبنية على Redis
Asynq من المطور hibiken هي، بلا شك، المكتبة الأكثر شيوعًا لقوائم المهام في Go حاليًا. وهي مبنية فوق Redis وتقدم وظائف غنية "مباشرة من الصندوق".
الميزات الرئيسية لـ Asynq:
- المراقبة: واجهة ويب مدمجة (Asynqmon) لتتبع القوائم وتنفيذ المهام والأخطاء.
- الجدولة: إمكانية تأجيل تنفيذ مهمة لوقت محدد (صيغة تشبه cron).
- الأولويات: دعم قوائم متعددة بأولويات مختلفة.
- إعادة المحاولة والرسائل الميتة: محاولات إعادة تلقائية وقائمة "الرسائل الميتة" للمهام غير القابلة للتنفيذ.
- البرمجيات الوسيطة: دعم البرمجيات الوسيطة لتسجيل السجلات والمقاييس وما إلى ذلك.
مثال على استخدام Asynq:
package main
import ( "context" "log" "time" "github.com/hibiken/asynq")
// تعريف نوع المهمةconst TypeEmailDelivery = "email:deliver"
// العميل لإرسال المهامfunc main() { client := asynq.NewClient(asynq.RedisClientOpt{Addr: "localhost:6379"}) defer client.Close()
// إنشاء مهمة task, err := asynq.NewTask(TypeEmailDelivery, []byte(`{"user_id": 42, "template": "welcome"}`)) if err != nil { log.Fatal(err) }
// إرسال المهمة مع تأخير لمدة 5 دقائق info, err := client.Enqueue(task, asynq.ProcessIn(5*time.Minute)) if err != nil { log.Fatal(err) } log.Printf("تم وضع المهمة [%s] في قائمة الانتظار", info.ID)}
// العامل لمعالجة المهامfunc handler(ctx context.Context, t *asynq.Task) error { log.Printf("معالجة المهمة: %s", t.Type()) // هنا منطق إرسال البريد الإلكتروني return nil}
func startWorker() { srv := asynq.NewServer( asynq.RedisClientOpt{Addr: "localhost:6379"}, asynq.Config{Concurrency: 10}, )
mux := asynq.NewServeMux() mux.HandleFunc(TypeEmailDelivery, handler)
if err := srv.Run(mux); err != nil { log.Fatal(err) }}Asynq مثالي لمعظم المشاريع التي تستخدم Redis بالفعل. إنه سهل الإعداد ويوفر تجربة مستخدم ممتازة للمطور.
2. Machinery: المهام الموزعة مع وسطاء الرسائل
Machinery هي مكتبة أقدم، لكنها قوية جدًا، مستوحاة من مكتبة Python Celery. على عكس Asynq، فإن Machinery ليست مرتبطة بشكل صارم بـ Redis وتدعم عدة وسطاء رسائل: Redis، RabbitMQ، AWS SQS و Memory.
الميزات الرئيسية لـ Machinery:
- مرونة الوسطاء: سهولة التبديل بين وسطاء الرسائل المختلفين.
- نتائج المهام: دعم مدمج لحفظ واسترجاع نتائج تنفيذ المهمة (على سبيل المثال، في Redis).