web-dev-qa-db-ja.com

クラスタモジュールを使用するNode.jsアプリケーションでCronジョブを実行するにはどうすればよいですか?

Node.jsアプリケーションでタスクをスケジュールするために node-cron モジュールを使用しています。また、コアクラスターモジュールを使用して、いくつかのプロセスでアプリケーションを実行したいと思います。

複数のプロセスでアプリケーションを実行すると、各プロセスでスケジュールされたタスクが実行されます(たとえば、タスクが電子メールを送信する場合、電子メールは複数回送信されます)。

クラスタモジュールと一緒にcronジョブを実行するためのベストプラクティス/可能な方法は何ですか? cronジョブのみを処理し、要求を受け入れない別のプロセスを作成する必要があります。はいの場合、どうすれば正しい方法でそれを行うことができますか?

22
epidemiya30

いくつかの調査の後、私は " 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...
12
epidemiya30

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公式ドキュメントの詳細 ここ

20
Rahul Kumar

私は実際には、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();
        }
    });
}
2
rcpfuchs

クラスタモジュールにも問題があり、最終的に問題を解決するためのサンプル方法を見つけました。

マスタークラスターにcronJobを実行させます。

私のプロジェクトでは、Kueを使用してジョブを管理しています。 cronJobを実行すると、ジョブのリストが表示されます。

index.js

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();
});

cron.js

const cron = require('cron').CronJob;

const job = new cron('* * * * *', async () => {
  if (cluster.isMaster) {
    console.log('cron trigger');
  }
});

job.start();

この助けを願っています。

0
koobitor