Node.jsアプリケーションでタスクをスケジュールするために node-cron モジュールを使用しています。また、コアクラスターモジュールを使用して、いくつかのプロセスでアプリケーションを実行したいと思います。
複数のプロセスでアプリケーションを実行すると、各プロセスでスケジュールされたタスクが実行されます(たとえば、タスクが電子メールを送信する場合、電子メールは複数回送信されます)。
クラスタモジュールと一緒にcronジョブを実行するためのベストプラクティス/可能な方法は何ですか? cronジョブのみを処理し、要求を受け入れない別のプロセスを作成する必要があります。はいの場合、どうすれば正しい方法でそれを行うことができますか?
いくつかの調査の後、私は " Redisを使用した分散ロック "ソリューションに行き着きました。そのためのノードモジュールがあります: node-redis-warlock 。
この答えが他の誰かに役立つことを願っています。
[〜#〜]更新[〜#〜]。最小限のサンプルコード:
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。より詳細な例:
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...
PM2を使用している場合は、PM2によって提供される環境変数を使用できます。それ自体は_NODE_APP_INSTANCE
_と呼ばれ、PM22.5以上が必要です。
_NODE_APP_INSTANCE
_環境変数を使用して、プロセス間の違いを判別できます。たとえば、1つのプロセスでのみcronジョブを実行したい場合は、これを実行できます。
if(process.env.NODE_APP_INSTANCE == 0) { //schedule your cron job here since this part will be executed for only one cluster }
、
2つのプロセスが同じ番号を持つことはできないためです。
PM2公式ドキュメントの詳細 ここ 。
私は実際には、cron-cluster npmプラグインでも使用されているredisアプローチが好きではありません。なぜなら、そのredisサーバーをマシンで実行して維持したくないからです。
このアプローチについてあなたと話し合いたいと思います。
長所:redisを使用する必要はありません短所:cronジョブは常に同じワーカーで実行されます
私はこれだけにメッセージパッシングを使用します。他の目的で使用する場合は、次の情報を渡したいと思います。
if (cluster.isMaster) {
// Count the machine's CPUs
var cpuCount = require('os').cpus().length;;
// Create a worker for each CPU
for (var i = 0; i < cpuCount; i += 1) {
cluster.fork();
}
cluster.on('fork', (worker) => {
console.log("cluster forking new worker", worker.id);
});
// have a mainWorker that does the cron jobs.
var mainWorkerId = null;
cluster.on('listening', (worker, address) => {
console.log("cluster listening new worker", worker.id);
if(null === mainWorkerId) {
console.log("Making worker " + worker.id + " to main worker");
mainWorkerId = worker.id;
worker.send({order: "startCron"});
}
});
// Listen for dying workers if the mainWorker dies, make a new mainWorker
cluster.on('exit', function (worker, code, signal) {
console.log('Worker %d died :(', worker.id);
if(worker.id === mainWorkerId) {
console.log("Main Worker is dead...");
mainWorkerId = null;
}
console.trace("I am here");
console.log(worker);
console.log(code);
console.log(signal);
cluster.fork();
});
// Code to run if we're in a worker process
} else {
// other code like setup app and stuff
var doCron = function() {
// setup cron jobs...
}
// Receive messages from the master process.
process.on('message', function(msg) {
console.log('Worker ' + process.pid + ' received message from master.', message);
if(message.order == "startCron") {
doCron();
}
});
}
クラスタモジュールにも問題があり、最終的に問題を解決するためのサンプル方法を見つけました。
マスタークラスターにcronJobを実行させます。
私のプロジェクトでは、Kueを使用してジョブを管理しています。 cronJobを実行すると、ジョブのリストが表示されます。
global.cluster = require('cluster');
if (cluster.isMaster) {
const cpuCount = require('os').cpus().length;
for (let i = 0; i < cpuCount; i += 1) {
cluster.fork();
}
} else {
// start your express server here
require('./server')
}
cluster.on('exit', worker => {
logger.warn('Worker %d died :(', worker.id);
cluster.fork();
});
const cron = require('cron').CronJob;
const job = new cron('* * * * *', async () => {
if (cluster.isMaster) {
console.log('cron trigger');
}
});
job.start();
この助けを願っています。