Node.jsを使ったWebアプリを運用していると、たまにUncaughtExceptionが発生してアプリケーションが停止してしまうことがありました.
手動で再開するまで全くアプリが使えなくなってしまうのは問題なので、これに対処する方法をまとめました.
1.問題
Webアプリを実行していると、稀にuncaughtExceptionが発生してました.
Node.jsのドキュメントを確認すると、以下のような記述があります.
Event: ‘uncaughtException’
発生した例外がイベントループまでたどり着いた場合に生成されます。 もしこの例外に対するリスナーが加えられていれば、 デフォルトの動作 (それはスタックトレースをプリントして終了します) は起こりません。
一度uncaughtExceptionが発生してしまうと、デフォルトではアプリケーションが終了してしまいます.
常に提供したいサービスであれば、例外のデバッグを行いつつもとりあえずはアプリケーションを再起動する必要があります.
(* 取り返しがつかない処理が存在するアプリであれば、停止させたままとする方がいい場合もあります.問題の影響が把握できれば、一旦再起動して後でデータをリカバリするという考え方も有るかと思いますが)
2.解決法
- node.jsで利用できるモニタリングソフトウェアを利用する
有名なのはnodemon, foreverです.
-
- nodemon でアプリケーションを実行する例
nodemon ./server.js
- forever でアプリケーションを実行する例
forever server.js start
- cluster を利用して再起動する例
コーディング例は以下のようになります.
WORKERSの値若しくはcpuの数だけworkerプロセスを起動し、そのプロセスがエラーで落ちたときに代わりのプロセスを起動しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var cluster = require('cluster'); var workers = process.env.WORKERS || require('os').cpus().length; if (cluster.isMaster) { console.log('start cluster with %s workers', workers); //for (var i = 0; i < workers; i++) { for (var i = 0; i < 1; i++) { var workerPid = cluster.fork().process.pid; console.log('worker %s started.', workerPid); } cluster.on('exit', function(worker) { console.log('Worker ' + worker.id + ' died. restart...'); var workerPid = cluster.fork().process.pid; console.log('worker %s started.', workerPid); }); } else { // start server here } |
通常はこのいずれかで解決することが望ましいのですが、さくらインターネットのレンタルサーバではこれらの対処を行うことができませんでした.
サーバを立ち上げるとすぐ Killed: 9 と表示されていたため、おそらくプロセスの負荷が高すぎるために監視に引っかかったのだと思われます.
* 試しに2. のクラスター(プロセス)数を1つまで減らしましたが、killされてしまうことに代わりはありませんでした.元のアプリケーションに比べて監視用のプロセス分、負荷が上がっているためと思われます.
3.より負荷の少ない対処方法
ここではprocess.on(‘uncaughtException’, function(err){});を利用した例を紹介します.
但し、uncaughtExceptionを利用した例外処理は雑なものとなってしまうため公式ドキュメントではdomainの利用が推奨されています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var server; function startServer() { server = http.createServer(function (request, response) { // serverの処理 }); server.listen(8080); }; process.on('uncaughtException', function(err) { console.log('UncaughtException: ' + err.message); if (err.stack) { console.log('Stacks: ' + err.stack); } console.log('SHUTDOWN SERVER'); server.close(function restart() { console.log('RESTART SERVER'); startServer(); }); }); |
ポイントは、例外発生時に元のサーバをcloseしている点です.
これを忘れると既にAddressが利用されているためエラーが発生してしまいます.