I'm using node-cron module for scheduling tasks in Node.js application. I also want run the application in several processes using core cluster module.
Running application in several processes ends up in scheduled tasks execution in each process (e.g. if task was to send an email the email would be sent multiple times).
What are the best practices/possible ways of running cron job along with cluster module? Should I create some separate process which will handle only cron job and do not accept any requests. If yes, how can I do that in a right way?
Definition and Usage. The cluster module provides a way of creating child processes that runs simultaneously and share the same server port. Node.
A Look at ClusteringThe Node. js Cluster module enables the creation of child processes (workers) that run simultaneously and share the same server port. Each spawned child has its own event loop, memory, and V8 instance. The child processes use IPC (Inter-process communication) to communicate with the parent Node.
Node Schedule is a flexible cron-like and not-cron-like job scheduler for Node. js. It allows you to schedule jobs (arbitrary functions) for execution at specific dates, with optional recurrence rules. It only uses a single timer at any given time (rather than reevaluating upcoming jobs every second/minute).
If are using PM2, You can use an environment variable provided by PM2 itself called NODE_APP_INSTANCE
which requires PM2 2.5 or greater.
NODE_APP_INSTANCE
environment variable can be used to determine difference between process, for example you may want to run a cronjob only on one process, you can just do this
if(process.env.NODE_APP_INSTANCE == 0) { //schedule your cron job here since this part will be executed for only one cluster }
,
Since two processes can never have the same number.
More Info on PM2 official doc here.
After some research I ended up with "Distributed locks using Redis" solution. There is node module for that: node-redis-warlock.
Hope this answer will be useful for someone else.
UPDATE. Minimal sample code:
var Warlock = require('node-redis-warlock'), redis = require('redis'); // Establish a redis client redis = redis.createClient(); // and pass it to warlock var warlock = new Warlock(redis); function executeOnce (key, callback) { warlock.lock(key, 20000, function(err, unlock){ if (err) { // Something went wrong and we weren't able to set a lock return; } if (typeof unlock === 'function') { setTimeout(function() { callback(unlock); }, 1000); } }); } // Executes call back only once executeOnce('every-three-hours-lock', function(unlock) { // Do here any stuff that should be done only once... unlock(); });
UPDATE 2. More detailed example:
const CronJob = require('cron').CronJob; const Warlock = require('node-redis-warlock'); const redis = require('redis').createClient(); const warlock = new Warlock(redis); const async = require('async'); function executeOnce (key, callback) { warlock.lock(key, 20000, function(err, unlock) { if (err) { // Something went wrong and we weren't able to set a lock return; } if (typeof unlock === 'function') { setTimeout(function() { callback(unlock); }, 1000); } }); } function everyMinuteJobTasks (unlock) { async.parallel([ sendEmailNotifications, updateSomething, // etc... ], (err) => { if (err) { logger.error(err); } unlock(); }); } let everyMinuteJob = new CronJob({ cronTime: '*/1 * * * *', onTick: function () { executeOnce('every-minute-lock', everyMinuteJobTasks); }, start: true, runOnInit: true }); /* Actual tasks */ let sendEmailNotifications = function(done) { // Do stuff here // Call done() when finished or call done(err) if error occurred } let updateSomething = function(done) { // Do stuff here // Call done() when finished or call done(err) if error occurred } // etc...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With