Skip to content

Instantly share code, notes, and snippets.

@yosuke-furukawa
Created September 9, 2012 10:18
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yosuke-furukawa/3683651 to your computer and use it in GitHub Desktop.
Save yosuke-furukawa/3683651 to your computer and use it in GitHub Desktop.
コードの変更をキャッチして、GracefulなRestartをする方法
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length / 2;
var numWorkers = numCPUs <= 1 ? 2 : numCPUs;
var watch = require('watch');
var domain = require('domain');
var util = require('util');
var forceKilledWorkers = {};
// cluster destroy if timeout.
var destroy = function() {
if (cluster.isWorker) {
var timeout = setTimeout(function() {cluster.worker.destroy();}, 120000);
forceKilledWorkers[cluster.worker.id] = timeout;
}
};
var forkWorkers = function(num) {
var forkDomain = domain.create();
forkDomain.on('error', function(error) {
util.log("error occured when starting new workers.");
util.log(error);
});
forkDomain.run(function(){
for (var i = 0; i < num; i++) {
cluster.fork();
}
});
};
var disconnectCluster = function() {
var disconnectDomain = domain.create();
disconnectDomain.on('error', function(error) {
util.log("error occured when disconnecting new workers.");
util.log(error);
destroy();
});
disconnectDomain.run(function() {
cluster.disconnect();
});
};
var respawnWorkers = function(num) {
disconnectCluster();
forkWorkers(num);
};
watch.createMonitor(__dirname, function (monitor) {
monitor.on("created", function (f, stat) {
respawnWorkers(numWorkers);
});
monitor.on("changed", function (f, curr, prev) {
respawnWorkers(numWorkers);
});
monitor.on("removed", function (f, stat) {
respawnWorkers(numWorkers);
});
});
if (cluster.isMaster) {
forkWorkers(numWorkers);
cluster.on('disconnect', function(worker) {
util.log("worker("+worker.id+").disconnect " + worker.process.pid);
});
cluster.on('exit', function(worker, code, signal) {
if (!worker.suicide) {
cluster.fork();
} else {
util.log("worker("+worker.id+") is suicide.");
}
var timeout = forceKilledWorkers[worker.id];
if (timeout) {
clearTimeout(timeout);
delete forceKilledWorkers[worker.id];
}
util.log("worker("+worker.id+").exit " + worker.process.pid);
});
cluster.on('fork', function(worker) {
util.log("worker("+worker.id+").fork");
});
cluster.on('online', function(worker) {
util.log("worker("+worker.id+").online " + worker.process.pid);
});
cluster.on('listening', function(worker, address) {
util.log("worker("+worker.id+").listening " + address.address + ":" + address.port);
});
} else if (cluster.isWorker) {
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end("<html><body><h1>Hello World.</h1></body></html>\n");
});
server.listen(3000);
server.on('close', function() { process.exit(); });
} else {
util.log("unsupported cluster.");
}
@yosuke-furukawa
Copy link
Author

watchで実行中のフォルダを監視しつつ、変更があったらclusterをfork。その後、古い世代のやつはclusterをdisconnectする。
workerの再起動は出来るけど、master部分は再起動できないから、master部分の変更は読み込まれないけど。

@Jxck
Copy link

Jxck commented Sep 12, 2012

この場合は worker を一括で管理している(個別の管理はしてない) のと、
fork, disconnect が「失敗しないもの」という前提になっているので、
Graceful とは言い切れないかもしれません。

例えば、

cluster.disconnect();
forkWorkers(num);

ですかねぇ。

disconnect してすぐ死ななかったりとかあると、
一時的に worker の数が増えそう。

丁寧にやるなら disconnect, exit イベントを監視する感じでしょうか。

同様に、ちゃんと起動したことを確認するためには
fork, online イベントを監視する必要がありますね。

また、現在の API では isWorker があるので
else よりそっちを使うほうが、明示してるし個人的には良いかと。
あと、まあほぼ無いと思うけど、将来 master, worker 以外の何かが
出てきた場合云々もあるかって視点でも。

あと、今回は割りとふんだんにログを出しているようなので、
suiside あたりのログも出すといいのかも。

今回のサンプルは、フォルダの変更以外(シグナルなど)でリスタートする要因が無いので、
その辺はいいかもしれませんが、せっかくなので安全に Shutdown する方法なんかもあると
より参考になるかもです。(余力があればでいいですが。)

最後に、これは個人的に思うんですが

require('os').cpus().length;

Node のマニュアルが元で、こういうサンプルよく見るけど、
本当に全部使っちゃうの?
ってちょっと思いますw

mastar は fork したあとは割りと黙ってるけど、
サーバって他にもプロセス動いたりするんだから、
一個くらい開ければ的なことを思ったり。
運用は詳しくないけど、実際みんなどうしてんのかな?
qilin はオプションだけど。
(まあ本質じゃないですが、この一行が結構出回ってる感があるんで)

@yosuke-furukawa
Copy link
Author

Jxckさん、非常に非常に遅くなりましたが、修正してみました。

disconnect してすぐ死ななかったりとかあると、
一時的に worker の数が増えそう。

ベタ書きになっちゃいましたが、2分間、disconnectで失敗した後に強制終了させるようにしてみました。
同様にdomainでforkのエラーも検知するようにしてみました。

また、現在の API では isWorker があるので
else よりそっちを使うほうが、明示してるし個人的には良いかと。
あと、まあほぼ無いと思うけど、将来 master, worker 以外の何かが
出てきた場合云々もあるかって視点でも。

確かに。そうしましょう。でも万が一のworker、master以外の時はどうしようと思って、今は一応
ログに書くだけ、という対応になっています。

あと、今回は割りとふんだんにログを出しているようなので、
suiside あたりのログも出すといいのかも。

suicideもログに出してみました。
あと、suicideじゃないときはなんかの拍子に死んだ時なので、
その時はforkし直すようにしてみました。

Node のマニュアルが元で、こういうサンプルよく見るけど、
本当に全部使っちゃうの?
ってちょっと思いますw

var numCPUs = require('os').cpus().length / 2;
なんかで上のようにしているサンプルを見ました。
これだと半分しか使わない省エネ設定です。

他にも以下の様なのもみました。
var numCPUs = require('os').cpus().length - 2;
これだと必ず2個空けとくパターンですね。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment