محرك قائمة انتظار المهام المدعوم من Redis مع التحكم المتقدم في المهام والاتساق النهائي.
تجميع المهام، والتسلسل، والتكرارات لنطاقات ضخمة.
تشغيل المهمة المؤجلة والمجدولة.
توزيع الأحمال + تجمعات العمال.
من السهل تضمينها.
يوفر idoit
تحكمًا متقدمًا لتنفيذ ذلك
التجميع . مهمة group
خاصة تنفذ مهام الأطفال وتنتظر حتى يكتمل كل شيء. مفيد للخريطة/تقليل المنطق.
تسلسل . مهمة chain
خاصة تنفذ الأطفال واحدًا تلو الآخر. مفيد أيضًا لتصغير الخريطة أو تقسيم المهام المعقدة جدًا إلى خطوات أكثر بساطة.
مكرر رسم الخرائط . ميزة خاصة للحمولات الضخمة، لإنتاج قطع حسب الطلب. فوائد:
لا توجد تأخيرات في مرحلة رسم الخرائط، وتبدأ معالجة القطع على الفور.
من السهل تحسين استعلامات قاعدة البيانات لإنشاء أجزاء متساوية الحجم (التخطي + استعلامات الحد بطيئة جدًا في البيانات الضخمة).
تقدم . عند استخدام سيناريوهات المجموعات/السلسلة/الخريطة، يكون من السهل مراقبة التقدم الإجمالي عبر الوالدين الأعلى. يمكن للمهام المستقلة الطويلة أيضًا إخطار المستخدم بتغيير التقدم.
حمامات العمال . يمكنك تقسيم المهام عن طريق عمليات مختلفة. على سبيل المثال، إذا كنت لا ترغب في حجب المهام الثقيلة عن المهام الخفيفة.
شيدولر . يسمح cron المدمج بتنفيذ المهام وفقًا لجدول زمني محدد.
جميع البيانات الموجودة في redis متسقة دائمًا.
لا يمكن فقدان المهمة، ولكن يمكن تشغيلها مرتين في حالات الحافة (إذا تعطلت العملية عندما كانت وظيفة المهمة على وشك الانتهاء)
يمكن أن يتم احتساب التقدم "أسرع" في حالة استخدام task.progressAdd()
وتعطل العملية قبل اكتمال المهمة. ولكن هذا ليس بالغ الأهمية، حيث لا يمكن استخدام هذه المعلومات إلا لتحديثات أشرطة تقدم الواجهة. في معظم الحالات لن ترى الفرق.
مطلوب node.js
6+ و redis
3.0+.
npm تثبيت idoit --حفظ
redisURL (سلسلة) - عنوان URL لاتصال redis.
التزامن (الرقم) - الحد الأقصى للمهام التي يجب استهلاكها بالتوازي بواسطة عامل واحد، 100 بشكل افتراضي.
تجمع (سلسلة) - اسم تجمع العمال، "افتراضي" إذا لم يتم تعيينه. يُستخدم إذا كان مثيل قائمة الانتظار هذا يستهلك المهام فقط (بعد .start()
). يمكنك توجيه المهام إلى مجموعات محددة من العاملين لتجنب الأقفال غير المرغوب فيها. يمكنك تعيين pool
على Array، [ 'pool1', 'pool2' ]
لاستهلاك المهام من عدة تجمعات (لأغراض التطوير/الاختبار).
ns (سلسلة) - مساحة اسم البيانات، المستخدمة حاليًا كبادئة مفاتيح redis، "idoitqueue:" افتراضيًا.
من الممارسات الجيدة أن يكون لديك مجموعات عمال منفصلة لمهام الحظر الثقيلة والمهام غير المحظورة. على سبيل المثال، لا ينبغي لأحد أن يمنع إرسال رسائل البريد الإلكتروني العاجلة. لذلك، قم بإنشاء العديد من العمليات المنفذة، وقم بتثبيتها في مجموعات مختلفة وقم بتعيين التزامن المناسب للمهام. يمكن استخدام المهام غير المحظورة بالتوازي، ويمكنك أن تكون موافقًا على concurrency
الافتراضي = 100. يجب استهلاك المهام المحظورة واحدة تلو الأخرى، قم بتعيين concurrency
= 1 لهؤلاء العاملين.
ملحوظة. قد يحدث أن تقوم بإزالة بعض أنواع المهام من تطبيقك. في هذه الحالة سيتم مسح البيانات المعزولة بعد 3 أيام.
خيارات:
الاسم (سلسلة) - اسم المهمة.
baseClass (وظيفة) - اختياري، منشئ المهمة الأساسية، "المهمة" افتراضيًا.
init (وظيفة) - اختياري، يُستخدم لتهيئة المهام غير المتزامنة، ويجب أن يُرجع Promise
this (Object) - المهمة الحالية (إجمالي المهمة متاح كـ this.total
).
معرف المهمة (الوظيفة) - اختياري، يجب أن يُرجع معرف المهمة الجديد. مطلوب فقط لإنشاء مهام "حصرية"، وإرجاع قيمة عشوائية افتراضيًا، تسمى: function (taskData)
. السكر: إذا قمت بتمرير سلسلة عادية، فسيتم تغليفها بوظيفة، والتي تعيد هذه السلسلة دائمًا.
عملية (وظيفة) - وظيفة المهمة الرئيسية، تسمى باسم: task.process(...args)
. يجب أن يعود Promise
هذا (الكائن) - المهمة الحالية.
إعادة المحاولة (الرقم) - اختياري، عدد مرات إعادة المحاولة عند حدوث خطأ، الافتراضي 2.
retryDelay (الرقم) - اختياري، التأخير بالمللي ثانية بعد إعادة المحاولة، الافتراضي 60000 مللي ثانية.
المهلة (الرقم) - اختيارية، مهلة التنفيذ، الافتراضية 120000 مللي ثانية.
الإجمالي (الرقم) - اختياري، الحد الأقصى لقيمة التقدم، الافتراضي 1. إذا لم تقم بتعديل السلوك، يبدأ التقدم بـ 0 ويصبح 1 في نهاية المهمة.
تأجيل التأخير (الرقم) - اختياري، إذا تم استدعاء التأجيل دون تأخير، فمن المفترض أن يكون التأخير مساويًا لهذه القيمة (بالملي ثانية).
كرون (سلسلة) - اختياري، سلسلة كرون ("15 */6 * * *")، افتراضيا فارغة.
المسار (الرقم) - الافتراضي 3600000 مللي ثانية (ساعة واحدة). حان الوقت لتذكر المهام المجدولة من cron لتجنب إعادة التشغيل إذا كانت هناك ساعات خاطئة لدى العديد من الخوادم في المجموعة. لا تقم بتعيين قيمة عالية جدًا للمهام المتكررة جدًا، لأنها يمكن أن تشغل مساحة كبيرة من الذاكرة.
الحصول على المهمة عن طريق الهوية إرجاع وعد تم حله بمهمة أو بقيمة null
إذا لم تكن المهمة موجودة.
حقول المهام التي يمكنك استخدامها:
الإجمالي - إجمالي تقدم المهمة
التقدم - تقدم المهمة الحالية
النتيجة - نتيجة المهمة
خطأ - خطأ المهمة
إلغاء المهمة. إرجاع وعد تم حله بالمهمة.
ملحوظة. يمكنك إلغاء المهام فقط بدون الوالدين.
ابدأ العامل وابدأ في استهلاك بيانات المهمة. Return Promise
، يتم حله عندما تكون قائمة الانتظار جاهزة (اتصل بـ .ready()
بالداخل).
إذا تم تحديد pool
في cunstructor، فسيتم استهلاك المهام الموجهة إلى هذا السحب فقط.
التوقف عن قبول المهام الجديدة من قائمة الانتظار. Promise
الإرجاع، يتم حله عند اكتمال كافة المهام النشطة في هذا العامل.
Promise
الإرجاع، يتم حله عندما تكون قائمة الانتظار جاهزة للعمل (بعد حدث "الاتصال"، انظر أدناه).
تحديث خيارات المنشئ، باستثناء redisURL.
idoit
هو مثيل EventEmitter
الذي يطلق بعض الأحداث:
ready
عندما يكون اتصال redis جاهزًا ويمكن تنفيذ الأوامر (يمكن تسجيل المهام بدون اتصال)
error
عند حدوث خطأ.
task:progress
، task:progress:<task_id>
> - عند تقدم تحديث المهمة. بيانات الحدث هي: {المعرف، uid، الإجمالي، التقدم }
task:end
، task:end:<task_id>
> - عند انتهاء المهمة. بيانات الحدث هي: {id,uid}
قم بإنشاء مهمة جديدة باستخدام المعلمات الاختيارية.
تجاوز خصائص المهمة. على سبيل المثال، قد ترغب في تعيين مهام مجموعة/سلسلة محددة إلى مجموعة أخرى.
تشغيل المهمة على الفور. إرجاع وعد تم حله بمعرف المهمة.
تأجيل تنفيذ المهمة delay
ميلي ثانية (أو إلى task.postponeDelay
).
إرجاع وعد تم حله بمعرف المهمة.
أعد تشغيل المهمة قيد التشغيل حاليًا.
add_retry (منطقي) - اختياري، سواء لزيادة عدد مرات إعادة المحاولة أم لا (الافتراضي: خطأ)
إذا كان true
، فسيتم زيادة عدد مرات إعادة المحاولة، ولن يتم إعادة تشغيل المهمة في حالة تجاوزها
إذا كانت false
، فسيظل عدد مرات إعادة المحاولة كما هو، بحيث يمكن للمهمة إعادة تشغيل نفسها إلى أجل غير مسمى
التأخير (الرقم) التأخير قبل إعادة التشغيل بالمللي ثانية (الافتراضي: task.retryDelay
).
لاحظ أن idoit
يحتوي بالفعل على منطق إعادة تشغيل مدمج بشأن أخطاء المهام. ربما لا يجب عليك استخدام هذه الطريقة مباشرة. يتم كشفه لحالات محددة للغاية.
زيادة تقدم المهمة الحالية.
إرجاع وعد تم حله بمعرف المهمة.
تحديث الموعد النهائي للمهمة الحالية.
إرجاع وعد تم حله بمعرف المهمة.
إنشاء مهمة جديدة، وتنفيذ الأطفال بالتوازي.
قائمة الانتظار.المجموعة([ قائمة الانتظار. الأطفال1 ()، قائمة الانتظار. الأطفال2 ()، queue.children3()]).run()
نتيجة المجموعة هي مجموعة غير مصنفة من النتائج الفرعية.
إنشاء مهمة جديدة، وتنفيذ الأطفال في سلسلة. إذا فشل أي من الأطفال - فستفشل السلسلة أيضًا.
queue.registerTask('multiply', (a, b) => a * b);queue.registerTask('subtract', (a, b) => a - b);queue.chain([ queue.multiply(2, 3), // 2 * 3 = 6 queue.subtract(10), // 10 - 6 = 4 queue.multiply(3) // 3 * 4 = 12]).run()
يتم تمرير نتيجة المهمة السابقة كوسيطة أخيرة للمهمة التالية. نتيجة السلسلة هي نتيجة المهمة الأخيرة في السلسلة.
طريقة خاصة لتشغيل خرائط ضخمة بأسلوب كسول (حسب الطلب). انظر التعليقات أدناه.
// تسجيل المكرر Taskqueue.registerTask({ الاسم: "lazy_mapper"، الفئة الأساسية: قائمة الانتظار.المكرر، // يتم استدعاء هذه الطريقة عند بدء المهمة وعند نهاية كل طفل. يمكن أن يكون // دالة منشئة أو دالة تُرجع "Promise". * تكرار (حالة) {// ...// ثلاثة أنواع من حالات الإخراج المحتملة: منتهية، لا تفعل شيئًا وبيانات جديدة.//// 1. `فارغة` - تم الوصول إلى النهاية، لا ينبغي استدعاء المكرر بعد الآن.// 2. `{}` - خامل، هناك ما يكفي من المهام الفرعية في قائمة الانتظار، حاول الاتصال // بالمكرر لاحقًا (عند انتهاء الطفل التالي).// 3. {// الحالة - حالة مكرر جديدة يجب تذكرها (على سبيل المثال، الإزاحة for// db query)، أي بيانات قابلة للتسلسل// مهام - مجموعة من المهام الفرعية الجديدة لدفعها إلى قائمة الانتظار// }//// هام! يمكن استدعاء التكرار بالتوازي من عمال مختلفين. نحن // نستخدم "الحالة" المدخلة لحل التصادمات عند تحديث redis. لذا، إذا قمت// بإنشاء مهام فرعية جديدة://// 1. يجب أن تكون "الحالة" الجديدة مختلفة (لجميع الحالات السابقة)// 2. يجب ألا تكون مصفوفة "المهام" فارغة.//// وفي حالة أخرى، يجب أن تشير إلى "النهاية" أو "الخمول". //// ستؤدي المجموعة غير الصالحة إلى حدوث "نهاية" + حدث خطأ.//return {state: newState, Tasks: ChunksArray}; }});// تشغيل iteratorqueue.lazy_mapper().run();
لماذا تم اختراع هذا السحر المجنون؟
تخيل أنك بحاجة إلى إعادة بناء 10 ملايين من مشاركات المنتدى. ترغب في تقسيم العمل إلى أجزاء صغيرة متساوية، لكن المنشورات لا تحتوي على تعداد صحيح متسلسل، فقط معرفات mongo. ماذا يمكنك أن تفعل؟
تعد طلبات skip
المباشر + limit
مكلفة للغاية بالنسبة للمجموعات الكبيرة في أي قاعدة بيانات.
لا يمكنك التقسيم حسب فترات زمنية، لأن كثافة المشاركات تختلف كثيرًا من أول مشاركة إلى آخر مشاركة.
يمكنك إضافة حقل مفهرس برقم عشوائي لكل مشاركة. ثم تقسيم على فترات. سيعمل ذلك، لكنه سيتسبب في الوصول العشوائي إلى القرص - ليس رائعًا.
الحل هو استخدام مخطط التكرار، والذي يمكنه تذكر "الموضع السابق". في هذه الحالة، ستفعل طلبات range
+ limit
بدلاً من skip
+ limit
. وهذا يعمل بشكل جيد مع قواعد البيانات. المكافآت الإضافية هي:
لا تحتاج إلى الاحتفاظ بجميع المهام الفرعية في قائمة الانتظار. على سبيل المثال، يمكنك إنشاء 100 قطعة وإضافة 100 قطعة تالية عندما تكون السابقة على وشك الانتهاء.
تصبح مرحلة رسم الخرائط موزعة ويمكنك البدء في مراقبة التقدم الإجمالي على الفور.
إعادة تشغيل Quik عبر عامل الإرساء:
# startdocker run -d -p 6379:6379 --name redis1 redis# stopdocker stop redis1 عامل ميناء rm redis1
لسبب ما، نحن على دراية بالكيو والكرفس والعكا. كان هدفنا هو تحقيق التوازن بين البساطة والقوة. لذلك، لا نعرف ما إذا كان idoit
يعمل بشكل جيد في مجموعة تحتوي على آلاف المثيلات. ولكن ينبغي أن يكون جيدًا في الأحجام الصغيرة كما أنه سهل الاستخدام حقًا.
لم يكن kue مناسبًا لاحتياجاتنا، للأسباب التالية:
إن مفهوم "الأولويات" الخاص به ليس مرنًا ولا يحمي جيدًا من الأقفال الناتجة عن المهام الثقيلة
لا يوجد تجميع/تسلسل للمهام وما إلى ذلك
لا توجد ضمانات قوية لاتساق البيانات
في idoit
نهتم بما يلي:
عمليات مجموعة المهام/السلسلة وتمرير البيانات بين المهام (على غرار الكرفس)
تجمعات العمال لعزل تنفيذ المهام حسب الأنواع.
سهل الاستخدام والتثبيت (يلزم إعادة الاتصال فقط، ويمكن تشغيله في العملية الحالية)
الاتساق النهائي للبيانات المخزنة
السكر الأساسي مثل المجدول المدمج
مخطط تكراري للحمولات الضخمة (ميزة فريدة ومفيدة جدًا للعديد من مهام الصيانة)
تتبع تقدم المهمة
تجنب الأقفال العالمية
لا يزال بإمكان Redis أن يكون نقطة فشل، لكن هذا سعر مقبول للبساطة. لسبب أنه يمكنك الحصول على توفر أفضل عبر ناقلات الرسائل الموزعة مثل RMQ. ولكن في كثير من الحالات يكون من المهم إبقاء الأمور بسيطة. مع idoit
يمكنك إعادة استخدام التقنيات الموجودة دون نفقات إضافية.