أدوات مطور Node.js طابور الوظيفة

عند تطبيق الواجهة الخلفية لتطبيقات الويب وتطبيقات الهاتف المحمول ، وحتى أبسطها ، أصبح من المعتاد استخدام أدوات مثل: قواعد البيانات ، خادم البريد (smtp) ، خادم redis. مجموعة الأدوات المستخدمة تتوسع باستمرار. على سبيل المثال ، يتم استخدام قوائم انتظار الرسائل ، بناءً على عدد عمليات تثبيت حزمة amqplib (650 ألف تثبيت في الأسبوع) ، جنبًا إلى جنب مع قواعد البيانات العلائقية (حزمة mysql 460 ألف عملية في الأسبوع و 800 800 عملية تثبيت في الأسبوع).

اليوم أريد أن أتحدث عن قوائم انتظار الوظائف ، والتي تستخدم حتى الآن بترتيب أقل من الحجم ، على الرغم من أن الحاجة إليها تنشأ ، في جميع المشاريع الحقيقية تقريبًا

لذلك ، تسمح لك طوابير الوظائف بأداء بعض المهام بشكل غير متزامن ، في الواقع ، تؤدي وظيفة مع معلمات الإدخال المحددة وفي الوقت المحدد.

اعتمادًا على المعلمات ، يمكن تنفيذ المهمة:

  • مباشرة بعد إضافة إلى قائمة انتظار الوظائف ؛
  • مرة واحدة في وقت محدد ؛
  • عدة مرات في الموعد المحدد.

تسمح لك قوائم انتظار الوظائف بنقل المعلمات إلى وظيفة يتم تشغيلها ، وتعقب وإعادة تشغيل المهام التي تفشل ، ووضع حد لعدد الوظائف التي تعمل في وقت واحد.

ترتبط الغالبية العظمى من التطبيقات على Node.js بتطوير REST-API لتطبيقات الويب والجوال. يعد تقليل وقت تنفيذ REST-API أمرًا مهمًا لعمل مريح للمستخدم مع التطبيق. في الوقت نفسه ، يمكن أن تبدأ مكالمة إلى REST-API عمليات طويلة و / أو كثيفة الاستخدام للموارد. على سبيل المثال ، بعد إجراء عملية شراء ، يجب أن ترسل للمستخدم رسالة دفع إلى تطبيق الهاتف المحمول ، أو إرسال طلب لإجراء عملية شراء على CRM REST-API. يمكن تنفيذ هذه الاستعلامات بشكل غير متزامن. كيفية القيام بذلك بشكل صحيح إذا لم يكن لديك أداة للعمل مع قوائم انتظار الوظائف؟ على سبيل المثال ، يمكنك إرسال رسالة إلى قائمة انتظار الرسائل ، وبدء العامل الذي سيقرأ هذه الرسائل ويقوم بالأعمال اللازمة بناءً على هذه الرسائل.

في الواقع ، هذا ما تفعله طوابير العمل. ومع ذلك ، إذا نظرت عن كثب ، فإن قوائم الوظائف لديها عدة اختلافات جوهرية من قائمة انتظار الرسائل. أولاً ، يتم وضع الرسائل (ثابتة) في قائمة انتظار الرسائل ، وتتضمن قوائم انتظار العمل نوعًا من العمل (استدعاء دالة). ثانياً ، تتضمن قائمة انتظار المهمة وجود بعض المعالجات (العامل) الذي سيؤدي العمل المحدد. في هذه الحالة ، هناك حاجة إلى وظائف إضافية. يجب تحجيم عدد معالجات المعالج بشفافية في حالة زيادة الحمل. من ناحية أخرى ، من الضروري الحد من عدد المهام قيد التشغيل في وقت واحد على عامل معالج واحد من أجل تهدئة أحمال الذروة ومنع رفض الخدمة. هذا يدل على أن هناك حاجة إلى أداة يمكنها تشغيل مهام غير متزامنة من خلال تعيين معلمات مختلفة ، بسهولة مثل تقديم طلب باستخدام REST-API (أو أفضل إذا كان الأمر أسهل).

باستخدام قوائم انتظار الرسائل ، من السهل نسبيًا تنفيذ قائمة انتظار المهمة التي يتم تشغيلها مباشرة بعد وضع قائمة انتظار المهمة. ولكن غالبًا ما يلزم إكمال المهمة مرة واحدة في وقت محدد أو وفقًا لجدول زمني. بالنسبة لهذه المهام ، يتم استخدام عدد من الحزم التي تنفذ منطق cron في نظام التشغيل linux. لكي لا تكون بلا أساس ، سأقول أن حزمة العقدة كرون تحتوي على 480 ألف عملية في الأسبوع ، والجدول الزمني للعقدة - 170 ألف عملية في الأسبوع.

إن استخدام node-cron هو ، بطبيعة الحال ، أكثر ملاءمة من setInterval () الزهد ، لكنني شخصياً واجهت عددًا من المشاكل عند استخدامه. إذا كان التعبير عن عيب عام ، فإن هذا هو الافتقار إلى التحكم في عدد المهام المنفذة في وقت واحد (هذا يحفز أحمال الذروة: زيادة الحمل يبطئ عمل المهام ، يؤدي إبطاء عمل المهام إلى زيادة عدد المهام المنفذة في وقت واحد ، والتي بدورها تقوم بتحميل النظام أكثر) ، عدم القدرة على تشغيل العقدة لزيادة الإنتاجية - كرون على عدة نوى (في هذه الحالة ، يتم تنفيذ جميع المهام بشكل مستقل على كل جوهر) ونقص الأدوات لتعقب وإعادة تشغيل المهام التي أنجزت شيا مع وجود خطأ.

آمل أن أكون قد أوضحت أن الحاجة إلى أداة مثل قائمة انتظار الوظائف على قدم المساواة مع أدوات مثل قواعد البيانات. وقد ظهرت هذه الأموال ، على الرغم من أنها لم تستخدم بعد على نطاق واسع. سوف أدرج الأكثر شعبية منهم:

اسم الحزمةعدد المنشآت في الأسبوععدد الإعجابات
كوي291908753
طابور النحللا توجد معلومات1431
جدول أعمال254595488
ثور562325909


اليوم سوف أفكر في استخدام حزمة الثور ، والتي أعمل مع نفسي. لماذا اخترت هذه الحزمة المحددة (على الرغم من أنني لا أفرض خياري على الآخرين). في تلك اللحظة ، عندما بدأت أبحث عن تنفيذ مناسب لقائمة انتظار الرسائل ، تم إيقاف مشروع قائمة انتظار النحل بالفعل. تطبيق kue ، وفقًا للمعايير المعطاة في مستودع قائمة انتظار النحل ، تأخر كثيراً عن التطبيقات الأخرى ، بالإضافة إلى ذلك ، لم يتضمن وسائل تشغيل المهام المنفذة بشكل دوري. ينفذ مشروع جدول الأعمال قوائم الانتظار مع تخزين في قاعدة بيانات mongodb. هذه ميزة إضافية كبيرة في بعض الحالات ، إذا كنت بحاجة إلى الموثوقية الفائقة عند وضع المهام في قائمة الانتظار. ومع ذلك ، ليس هذا هو العامل الحاسم. وبطبيعة الحال ، اختبرت جميع خيارات التحمل في المكتبة ، حيث تولدت عددًا كبيرًا من المهام في قائمة الانتظار ، وما زلت لا أستطيع الحصول على عمل دون انقطاع من جدول الأعمال. عند تجاوز عدد معين من المهام ، توقف جدول الأعمال عن تشغيل المهام وتوقف عن العمل.

لذلك ، استقرت على الثور الذي ينفذ واجهة برمجة تطبيقات ملائمة ، مع السرعة والقابلية للتوسع ، حيث أن حزمة الثور تستخدم خادم redis كواجهة خلفية. على وجه الخصوص ، يمكنك استخدام مجموعة من خوادم redis.

عند إنشاء قائمة انتظار ، من المهم تحديد معلمات قائمة انتظار الوظيفة المثلى. هناك العديد من المعلمات ، ولم تصلني قيمة بعضها على الفور. بعد العديد من التجارب ، استقرت على المعلمات التالية:

const Bull = require('bull'); const redis = { host: 'localhost', port: 6379, maxRetriesPerRequest: null, connectTimeout: 180000 }; const defaultJobOptions = { removeOnComplete: true, removeOnFail: false, }; const limiter = { max: 10000, duration: 1000, bounceBack: false, }; const settings = { lockDuration: 600000, // Key expiration time for job locks. stalledInterval: 5000, // How often check for stalled jobs (use 0 for never checking). maxStalledCount: 2, // Max amount of times a stalled job will be re-processed. guardInterval: 5000, // Poll interval for delayed jobs and added jobs. retryProcessDelay: 30000, // delay before processing next job in case of internal error. drainDelay: 5, // A timeout for when the queue is in drained state (empty waiting for jobs). }; const bull = new Bull('my_queue', { redis, defaultJobOptions, settings, limiter }); module.exports = { bull }; 

في الحالات البسيطة ، ليست هناك حاجة لإنشاء العديد من قوائم الانتظار ، لأنه في كل قائمة انتظار يمكنك تحديد أسماء لمهام مختلفة ، وربط عامل المعالج بكل اسم:

 const { bull } = require('../bull'); bull.process('push:news', 1, `${__dirname}/push-news.js`); bull.process('push:status', 2, `${__dirname}/push-status.js`); ... bull.process('some:job', function(...args) { ... }); 

أنا أغتنم الفرصة التي تذهب إلى الثور "من خارج منطقة الجزاء" - لموازنة عمال المعالجات على عدة نوى. للقيام بذلك ، تقوم المعلمة الثانية بتعيين عدد النوى التي سيتم تشغيل عامل المعالج عليها ، وفي المعلمة الثالثة ، اسم الملف مع تعريف وظيفة معالجة المهمة. إذا لم تكن هذه الميزة مطلوبة ، يمكنك ببساطة تمرير وظيفة رد الاتصال باعتبارها المعلمة الثانية.

يتم وضع قائمة الانتظار المهمة بواسطة استدعاء الأسلوب add () ، والذي يتم تمرير اسم قائمة الانتظار والكائن في المعلمات ، والتي سيتم تمريرها فيما بعد إلى معالج المهام. على سبيل المثال ، في ربط ORM ، بعد إنشاء إدخال بأخبار جديدة ، يمكنني إرسال رسالة دفع بشكل غير متزامن إلى جميع العملاء:

  afterCreate(instance) { bull.add('push:news', _.pick(instance, 'id', 'title', 'message'), options); } 

يقبل معالج الأحداث في المعلمات كائن المهمة مع المعلمات التي تم تمريرها إلى أسلوب add () والدالة done () ، والتي يجب استدعاؤها لتأكيد المهمة كاملة أو لإعلام أن المهمة انتهت بخطأ:

 const { firebase: { admin } } = require('../firebase'); const { makePayload } = require('./makePayload'); module.exports = (job, done) => { const { id, title, message } = job.data; const data = { id: String(id), type: 'news', }; const payloadRu = makePayload(title.ru, message.ru, data); const payloadEn = makePayload(title.en, message.en, data); return Promise.all([ admin.messaging().send({ ...payloadRu, condition: "'news' in topics && 'ru' in topics" }), admin.messaging().send({ ...payloadEn, condition: "'news' in topics && 'en' in topics" }), ]) .then(response => done(null, response)) .catch(done); }; 

لعرض حالة قائمة انتظار المهمة ، يمكنك استخدام أداة bull-bull:

 const Arena = require('bull-arena'); const redis = { host: 'localhost', port: 6379, maxRetriesPerRequest: null, connectTimeout: 180000 }; const arena = Arena({ queues: [ { name: 'my_gueue', hostId: 'My Queue', redis, }, ], }, { basePath: '/', disableListen: true, }); module.exports = { arena }; 

وأخيرا ، اختراق الحياة قليلا. كما قلت ، يستخدم الثور خادم redis كخلفية. عند إعادة تشغيل خادم redis ، يكون احتمال اختفاء المهمة ضئيلًا للغاية. لكن مع معرفة حقيقة أن مسؤولي النظام يمكنهم أحيانًا "مسح ذاكرة التخزين المؤقت للفجل" ، مع حذف جميع المهام على وجه الخصوص ، كنت مهتمًا في المقام الأول بالمهام الجارية بشكل دوري ، والتي توقفت في هذه الحالة إلى الأبد. في هذا الصدد ، وجدت فرصة لاستئناف هذه المهام الدورية:

 const cron = '*/10 * * * * *'; const { bull } = require('./app/services/bull'); bull.getRepeatableJobs() .then(jobs => Promise.all(_.map(jobs, (job) => { const [name, cron] = job.key.split(/:{2,}/); return bull.removeRepeatable(name, { cron }); }))) .then(() => bull.add('check:status', {}, { priority: 1, repeat: { cron } })); setInterval(() => bull.add('check:status', {}, { priority: 1, repeat: { cron } }), 60000); 

بمعنى ، يتم استبعاد المهمة أولاً من قائمة الانتظار ، ثم تعيينها مرة أخرى ، وكل هذا (للأسف) بواسطة setInterval (). في الواقع ، بدون مثل هذا الاختراق في الحياة ، ربما لم أكن لأقرر استخدام المهام الدورية في الثور.

apapacy@gmail.com
3 يوليو 2019

Source: https://habr.com/ru/post/ar458608/


All Articles