Al implementar el back-end de aplicaciones web y aplicaciones móviles, incluso las más simples, se ha acostumbrado a usar herramientas tales como: bases de datos, servidor de correo (smtp), servidor redis. El conjunto de herramientas utilizadas se expande constantemente. Por ejemplo, las colas de mensajes, a juzgar por el número de instalaciones del paquete
amqplib (650 mil instalaciones por semana), se utilizan junto con bases de datos relacionales (paquete mysql 460 mil instalaciones por semana y pg 800 mil instalaciones por semana).
Hoy quiero hablar sobre las colas de trabajo, que hasta ahora se usan un orden de magnitud menor, aunque surge la necesidad de ellas, en casi todos los proyectos reales.
Entonces, las colas de trabajos le permiten realizar algunas tareas de forma asincrónica, de hecho, realizar una función con los parámetros de entrada dados y en el tiempo establecido.
Dependiendo de los parámetros, la tarea se puede realizar:
- inmediatamente después de agregar a la cola de trabajos;
- una vez a la hora establecida;
- Muchas veces a tiempo.
Las colas de trabajos le permiten transferir parámetros a un trabajo que se está ejecutando, rastrear y volver a ejecutar trabajos que han fallado, y establecer un límite en el número de trabajos que se ejecutan simultáneamente.
La gran mayoría de las aplicaciones en Node.js están asociadas con el desarrollo de una API REST para aplicaciones web y móviles. Reducir el tiempo de ejecución de REST-API es importante para un trabajo cómodo del usuario con la aplicación. Al mismo tiempo, una llamada a la REST-API puede iniciar operaciones largas y / o intensivas en recursos. Por ejemplo, después de realizar una compra, debe enviar al usuario un mensaje push a la aplicación móvil, o enviar una solicitud para realizar una compra en la REST-API de CRM. Estas consultas se pueden realizar de forma asincrónica. ¿Cómo hacerlo correctamente si no tiene una herramienta para trabajar con colas de trabajos? Por ejemplo, puede enviar un mensaje a la cola de mensajes, iniciar un trabajador que leerá estos mensajes y realizará el trabajo necesario en función de estos mensajes.
De hecho, esto es lo que hacen las colas de trabajo. Sin embargo, si observa detenidamente, las colas de trabajos tienen varias diferencias fundamentales con respecto a la cola de mensajes. En primer lugar, los mensajes (estáticos) se colocan en la cola de mensajes, y las colas de trabajos implican algún tipo de trabajo (llamada de función). En segundo lugar, la cola de trabajos implica la presencia de algún procesador (trabajador) que realizará el trabajo dado. En este caso, se necesita funcionalidad adicional. El número de procesadores del procesador debe ajustarse de forma transparente en caso de una carga aumentada. Por otro lado, es necesario limitar el número de tareas que se ejecutan simultáneamente en un procesador-trabajador para suavizar las cargas máximas y evitar la denegación de servicio. Esto muestra que existe la necesidad de una herramienta que pueda ejecutar tareas asincrónicas mediante el establecimiento de varios parámetros, tan fácil como hacer una solicitud utilizando la REST-API (o mejor si es aún más fácil).
Al usar las colas de mensajes, es relativamente sencillo implementar una cola de trabajos que se ejecuta inmediatamente después de que un trabajo está en cola. Pero a menudo se requiere completar la tarea una vez a una hora establecida o de acuerdo con un cronograma. Para estas tareas, se utilizan varios paquetes que implementan la lógica cron en Linux. Para no ser infundado, diré que el paquete nodo-cron tiene 480 mil instalaciones por semana, nodo-horario - 170 mil instalaciones por semana.
El uso de node-cron es, por supuesto, más conveniente que el conjunto ascético Interval (), pero personalmente, he encontrado una serie de problemas al usarlo. Si para expresar un inconveniente general, esta es la falta de control sobre el número de tareas ejecutadas simultáneamente (esto estimula las cargas máximas: aumentar la carga ralentiza el trabajo de las tareas, ralentizar las tareas aumenta el número de tareas ejecutadas simultáneamente, lo que a su vez carga el sistema aún más), la incapacidad de ejecutar el nodo para aumentar la productividad -cron en varios núcleos (en este caso, todas las tareas se ejecutan independientemente en cada núcleo) y la falta de herramientas para rastrear y reiniciar las tareas que se han completado Xia con un error.
Espero haber demostrado que la necesidad de una herramienta como la cola de trabajos está a la par con herramientas como las bases de datos. Y tales fondos han aparecido, aunque todavía no se usan ampliamente. Voy a enumerar los más populares de ellos:
Hoy consideraré el uso del paquete del toro, que trabajo conmigo mismo. ¿Por qué elegí este paquete en particular (aunque no impongo mi elección a los demás)? En ese momento, cuando comencé a buscar una implementación conveniente de la cola de mensajes, el proyecto de la cola de abejas ya estaba detenido. La implementación de kue, de acuerdo con los puntos de referencia proporcionados en el repositorio de la cola de abejas, quedó muy por detrás de otras implementaciones y, además, no contenía los medios para ejecutar tareas ejecutadas periódicamente. El proyecto de agenda implementa colas con almacenamiento en la base de datos mongodb. Esta es una gran ventaja para algunos casos, si necesita una súper confiabilidad al colocar tareas en la cola. Sin embargo, esto no solo es un factor decisivo. Naturalmente, probé todas las opciones de resistencia de la biblioteca, generando una gran cantidad de tareas en la cola, y todavía no podía obtener un trabajo ininterrumpido de la agenda. Al exceder un cierto número de tareas, la agenda se detuvo y dejó de poner tareas a trabajar.
Por lo tanto, me decidí por bull que implementa una API conveniente, con suficiente velocidad y escalabilidad, ya que el paquete bull utiliza un servidor redis como back-end. En particular, puede usar un clúster de servidores redis.
Al crear una cola, es muy importante seleccionar los parámetros óptimos para la cola de trabajos. Hay muchos parámetros, y el valor de algunos de ellos no me llegó de inmediato. Después de numerosos experimentos, me decidí por los siguientes parámetros:
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,
En casos triviales, no es necesario crear muchas colas, ya que en cada cola puede especificar nombres para diferentes tareas y asociar un procesador-trabajador con cada nombre:
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) { ... });
Aprovecho la oportunidad que se dirige al toro "fuera de la caja", para paralelizar a los trabajadores del procesador en varios núcleos. Para hacer esto, el segundo parámetro establece el número de núcleos en los que se iniciará el procesador-trabajador, y en el tercer parámetro, el nombre del archivo con la definición de la función de procesamiento del trabajo. Si no se necesita dicha función, simplemente puede pasar una función de devolución de llamada como segundo parámetro.
La tarea se pone en cola mediante una llamada al método add (), al que se pasan el nombre de la cola y el objeto en los parámetros, que posteriormente se pasarán al manejador de tareas. Por ejemplo, en un enlace ORM, después de crear una entrada con nuevas noticias, puedo enviar un mensaje push a todos los clientes de forma asíncrona:
afterCreate(instance) { bull.add('push:news', _.pick(instance, 'id', 'title', 'message'), options); }
El controlador de eventos acepta en los parámetros el objeto de tarea con los parámetros pasados al método add () y la función done (), a la que se debe llamar para confirmar que la tarea está completa o para informar que la tarea finalizó con un error:
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); };
Para ver el estado de la cola de trabajos, puede usar la herramienta arena-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 };
Y finalmente, un pequeño truco de vida. Como dije, Bull usa un servidor redis como backend. Cuando se reinicia el servidor redis, la probabilidad de que el trabajo desaparezca es muy pequeña. Pero sabiendo el hecho de que los administradores del sistema a veces pueden simplemente "borrar el caché de rábano", mientras eliminan todas las tareas en particular, me preocupaba principalmente la ejecución periódica de tareas, que en este caso se detuvo para siempre. En este sentido, encontré una oportunidad para reanudar tales tareas periódicas:
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);
Es decir, la tarea se excluye primero de la cola, y luego se establece de nuevo, y todo esto (por desgracia) por setInterval (). En realidad, sin ese truco de vida, probablemente no habría decidido usar tareas periódicas en toro.
apapacy@gmail.com
3 de julio de 2019