為何要進行內存超限重啟

在代碼層面,我們有很多種方式來防止NodeJS在執行的過程中出現內存泄露。但是當業務邏輯複雜的情況下,很難做到完全無內存泄露。這時候,一個偷懶的辦法就是要讓服務進程在超過一定內存閾值的時候重啟。

直接殺死進程的方式是不推薦的,因為會影響正在處理中的 request。在 cluster 模式下,node對每一個 fork 出的子進程提供了優雅的退出方式:

cluster.worker.disconnect()

他會保證子進程退出前處理完所有正在進行中的 request。

所以,我們可以在內存超出一定閾值的時候,調用disconnect方法。

如何檢測子進程內存佔用量

使用usage模塊可以檢測進程內存佔用量(PS,在 node 0.12 版本下未能安裝,在 4.x 版本下可以):

usage.lookup(process.pid, function(err, result) {

if (result === null || result === undefined) {
console.log("memory check fail");
return;
}

//Bytes
console.info(parseInt(result.memory));

});

在內存超限時重啟

在內存超過一定閾值的情況下重啟子進程的 cluster 模式下的進程管理的代碼如下:

//in memory.js

var cluster = require(cluster);
var usage = require(usage);
var os = require(os);

var CPU_COUNT = process.env.CPU_COUNT;
var CHECK_INTERVAL = process.env.CHECK_INTERVAL;

var cpuCount = CPU_COUNT || os.cpus().length;
var checkInterval = CHECK_INTERVAL || 5000;

module.exports = {
run: function(bytes, runFunc, cleanFunc) {

if (cluster.isMaster) {
for (var i = 0; i < cpuCount; i++) {
cluster.fork();
}
//注意點B
cluster.on(disconnect, function(worker) {
console.log( + worker.id + disconnect, restart now);
worker.isSuicide = true;
cluster.fork();
});
cluster.on(exit, function(worker) {
if (worker.isSuicide) {
console.info(process exit by kill);
} else {
console.info(process exit by accident);
cluster.fork();
}
console.info(process exit);
});
} else {

runFunc && runFunc();

var checkTimer = setInterval(function() {

usage.lookup(process.pid, function(err, result) {

if (result === null || result === undefined) {
console.log("memory check fail");
return;
}
if (parseInt(result.memory) > bytes) {
console.log("memory exceed, start to kill");

//注意點A
var killtimer = setTimeout(function() {
console.info("process down!")
process.exit(1);
}, 5000);
killtimer.unref();

cleanFunc && cleanFunc();

try {
if ([disconnected, dead].indexOf(cluster.workder.state) < 0) {
cluster.worker.disconnect();
}
} catch (err) {};

clearInterval(checkTimer);
}
});

}, checkInterval);
}

}
}

代碼裏有兩個注意點:

注意點A

其實這裡是參考了 domain 的文檔:Node.js v7.9.0 Documentation,我們這裡設置若干秒後將進程推出。

但是我們發現,這裡調用了 killtimer.unref(),這裡是為了防止定時器的存在阻止程序退出(PS:國內幾乎所有的書都在說 unref 的作用是阻止回調調用,其實不然,timer 的 unref 函數)。

注意點B

disconnect調用時,我們就要 fork 出新的子進程,但是有些情況下,進程會意外退出。在意外退出時,我們也要 fork 新的子進程補位,這時候就要區分到底是意外退出還是程序退出,否則就會 fork 冗餘的子進程。

cluster 的官方文檔有說明可以通過worker.suicide和worker.exitedAfterDisconnect來判斷進程是否是意外退出,但遺憾的是,node 的某些中間版本因為 bug (比如 4.x)失去了對這兩個flag的支持,所以這裡我們通過自己設置標誌位isSuicide來判斷是否意外退出。

使用方法

直接調用 export 出的 run 方法,設置超限閾值,設置子進程服務,設置重啟回調:

var memory = require(./memory);

memory.run(400000000, function() {
require(./server.js);
}, function() {
console.info(clean now!);
});

這裡要注意的是,內存的閾值要低於總內存/進程數,要給重啟時 fork 的新進程(因為那時候的老進程還未退出)以及系統上的其他服務留有內存空間。

最後一句話:重啟大法雖然好,但是也要防止內存泄露。

推薦閱讀:

查看原文 >>
相關文章