如何防止node.js崩溃?try-catch无效。

189

根据我的经验,在 PHP 服务器中,异常会被记录到日志或者返回给服务器端,但是在 Node.js 中程序会直接崩溃。即使将代码放在 try-catch 中也不起作用,因为所有操作都是异步进行的。我想知道其他人在生产服务器上是如何处理这个问题的。

10个回答

158

PM2

首先,我强烈建议安装 PM2 用于 Node.js。PM2 在处理崩溃和监控 Node 应用程序以及负载平衡方面非常出色。 PM2 可以在 Node 应用程序崩溃、因任何原因停止或甚至服务器重新启动时立即启动该应用程序。因此,即使在管理我们的代码后,应用程序崩溃,PM2 也可以立即重新启动它。有关更多信息,请参见安装和运行 PM2

其他答案真的很疯狂,您可以在 Node 的文档中阅读到:http://nodejs.org/docs/latest/api/process.html#process_event_uncaughtexception

如果有人使用其他陈述的答案,请阅读 Node 文档:

请注意,uncaughtException 是一种非常粗糙的异常处理机制,可能会在将来被删除

现在回到我们防止应用程序本身崩溃的解决方案。

因此,在经过研究后,我最终想到了 Node 文档本身建议的方法:

不要使用 uncaughtException,而是使用带有 clusterdomains。如果您确实使用 uncaughtException,请在每次未处理的异常后重新启动应用程序!

DOMAINCluster

我们实际上是将错误响应发送到触发错误的请求,同时让其他请求按照正常时间完成,并停止在该工作进程中监听新请求。

这样,域使用与集群模块相辅相成,因为主进程可以在工作进程遇到错误时派生新的工作进程。请参见下面的代码,以了解我的意思

通过使用 Domain 和将程序分成多个工作进程的弹性,使用 Cluster,我们可以更适当地做出反应,并以更高的安全性处理错误。

var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;

if(cluster.isMaster) 
{
   cluster.fork();
   cluster.fork();

   cluster.on('disconnect', function(worker) 
   {
       console.error('disconnect!');
       cluster.fork();
   });
} 
else 
{
    var domain = require('domain');
    var server = require('http').createServer(function(req, res) 
    {
        var d = domain.create();
        d.on('error', function(er) 
        {
            //something unexpected occurred
            console.error('error', er.stack);
            try 
            {
               //make sure we close down within 30 seconds
               var killtimer = setTimeout(function() 
               {
                   process.exit(1);
               }, 30000);
               // But don't keep the process open just for that!
               killtimer.unref();
               //stop taking new requests.
               server.close();
               //Let the master know we're dead.  This will trigger a
               //'disconnect' in the cluster master, and then it will fork
               //a new worker.
               cluster.worker.disconnect();

               //send an error to the request that triggered the problem
               res.statusCode = 500;
               res.setHeader('content-type', 'text/plain');
               res.end('Oops, there was a problem!\n');
           } 
           catch (er2) 
           {
              //oh well, not much we can do at this point.
              console.error('Error sending 500!', er2.stack);
           }
       });
    //Because req and res were created before this domain existed,
    //we need to explicitly add them.
    d.add(req);
    d.add(res);
    //Now run the handler function in the domain.
    d.run(function() 
    {
        //You'd put your fancy application logic here.
        handleRequest(req, res);
    });
  });
  server.listen(PORT);
} 

尽管 Node 文档中已经说明,Domain 将被废弃并替换为新的 API,但目前仍可使用。

该模块即将废弃。一旦有了替代 API,该模块将被完全弃用。当前确实需要 Domain 功能的用户可能暂时仍需依赖它,但未来应该准备迁移到其他解决方案。

在新的替代品推出之前,根据 Node 文档,结合 Cluster 使用 Domain 是唯一好的解决方案。

如需深入了解 DomainCluster,请查看以下链接

https://nodejs.org/api/domain.html#domain_domain (稳定性: 0 - 废弃)

https://nodejs.org/api/cluster.html

感谢 @Stanley Luo 分享的关于 Cluster 和 Domains 的深入解释。

Cluster & Domains


9
警告:Domain 已经被弃用,详情请见链接。Node 文档建议使用 cluster 代替,请参考链接 - Paul
6
每次出现未处理的异常时,请重新启动您的应用程序!如果有2000个用户正在使用一个节点Web服务器进行视频流传输,并且1个用户发生异常,那么重新启动不会中断所有其他用户的服务。 - Vikas Bansal
2
@VikasBansal 是的,这肯定会打断所有用户,这就是为什么使用uncaughtException是不好的,而应该使用Cluster中的Domain,这样,如果一个用户遇到异常,只有他的线程从集群中删除并为他创建新的线程。而且你也不需要重新启动Node服务器。另一方面,如果你使用uncaughtException,每当你的任何用户遇到问题时,你都必须重新启动服务器。因此,请使用带有Cluster的Domain。 - Airy
4
domain被完全废弃和移除时,我们应该怎么办? - Jas
3
找到了一份教程,适合那些不理解“集群”和“工作进程”概念的人参考:https://www.sitepoint.com/how-to-create-a-node-js-cluster-for-speeding-up-your-apps/ - Stanley Luo
显示剩余14条评论

105

我把这段代码放在我的require语句和全局声明下面:

process.on('uncaughtException', function (err) {
  console.error(err);
  console.log("Node NOT Exiting...");
});

对我来说有效。唯一不喜欢的是,与其让东西崩溃,我得到的信息不够多。


54
注意:这种方法很有效,但请记住所有HTTP响应都需要正确结束。这意味着,如果在处理HTTP请求时发生未捕获的异常,仍必须在http.ServerResponse对象上调用end()。如何实现这取决于您。如果不这样做,请求将挂起直到浏览器放弃。如果有足够多的这些请求,服务器可能会耗尽内存。 - BMiner
4
@BMiner,你能提供一个更好的实现吗?我注意到了这个问题(请求挂起),所以这并不比只是使用 forever 或其他方法重新启动服务器要好。 - pixelfreak
7
这需要进行深入的解释。我知道这很糟糕,但是每当发生未捕获的异常时,您的服务器需要尽快重新启动。实际上,“uncaughtException”事件的目的是将其用作发送警告电子邮件的机会,然后使用“process.exit(1);”来关闭服务器。您可以使用 forever 或类似的工具来重新启动服务器。任何待处理的 HTTP 请求将超时并失败。您的用户将对您感到不满。但是,这是最好的解决方案。为什么?请查看 https://dev59.com/e2sz5IYBdhLWcg3wFUG_ - BMiner
3
要从未捕获的错误中获取更多信息,请使用:console.trace(err.stack); - Jesse Dunlap
3
警告:节点文档明确表示,您绝不能这样做,因为它非常危险:http://nodejs.org/api/process.html#process_event_uncaughtexception - Jeremy Logan
显示剩余4条评论

35

正如这里所提到的,你可以使用error.stack来获取更完整的错误信息,例如导致错误的行号:

process.on('uncaughtException', function (error) {
   console.log(error.stack);
});

11

试试supervisor吧。

npm install supervisor
supervisor app.js

或者您可以安装forever代替。

这只是在服务器崩溃时通过重启来恢复它。

forever可以在代码中使用,以优雅地恢复任何崩溃的进程。

forever文档提供了有关程序化退出/错误处理的可靠信息。


10
这肯定不是解决方法……服务器宕机期间无法响应新的请求。应用程序代码可能会抛出异常 - 服务器需要回应500错误,而不是崩溃并希望重新启动。 - Ant Kutschera
@AntKutschera,你应该在节点前面有一个集群/负载均衡器。永远不要运行单个实例,总是运行4个(在四核上)。 - Raynos
23
作为一名黑客,可以通过发送一个简单请求到服务器并省略请求参数来找出问题,这将导致 javascript 中的 undef 使 node.js 崩溃。根据你的建议,我可以重复杀死你的整个集群。解决方法是使应用程序具备优雅失败的能力——即处理未捕获的异常而不是崩溃。如果服务器正在处理许多 voip 会话呢?让它崩溃并导致所有现有会话终止是不可接受的,否则用户很快就会离开。 - Ant Kutschera
6
这就是为什么异常应该是特殊情况。异常只应在无法恢复且进程必须崩溃的情况下触发。您应该使用其他方法来处理这些“异常”情况。但我理解您的观点。在可能的情况下,您应该优雅地失败。然而,在存在损害更大的情况下,继续运行处于已损坏状态的情况是不可取的。 - Raynos
2
是的,这里有不同的思路。我学习它的方式(使用Java而不是Javascript)是,有可接受的期望,你应该期望它们,可能被称为业务异常,然后有运行时异常或错误,在这种情况下,你不应该期望恢复,例如内存不足。不优雅地失败的一个问题是,我编写的某个库可能会声明在可恢复的情况下抛出异常,比如用户可以更正他们的输入。在你的应用程序中,如果你没有阅读我的文档并崩溃,那么用户可能已经能够恢复。 - Ant Kutschera
1
@AntKutschera 这就是为什么我们记录异常的原因。您应该分析生产日志中的常见异常,并找出是否以及如何从中恢复,而不是让服务器崩溃。我已经在 PHP、Ruby on Rails 和 Node 中使用了这种方法。无论您是否退出进程,每次抛出 500 错误,都会给用户带来不便。这不是 JavaScript 或 Node 特定的做法。 - Eric Elliott

7
使用 try-catch 可以解决未捕获的错误,但在某些复杂情况下,比如捕获 async 函数时,它可能无法正确处理任务。请记住,在 Node 中,任何异步函数调用都可能包含潜在的导致应用崩溃的操作。
使用 uncaughtException 是一种解决方法,但被认为是低效的,并且有可能在未来版本的 Node 中被删除,因此不能依赖它。
理想的解决方案是使用 domain:http://nodejs.org/api/domain.html 为了确保您的应用程序处于运行状态,即使服务器崩溃,可以使用以下步骤:
  1. 使用 node cluster 每个核心派生多个进程。因此,如果一个进程死亡,则会自动启动另一个进程。查看:http://nodejs.org/api/cluster.html
  2. 使用 domain 捕获异步操作,而不是使用 try-catch 或 uncaught。我并不是说 try-catch 或 uncaught 是不好的思路!
  3. 使用 forever/supervisor 监控您的服务
  4. 添加守护进程来运行您的 Node 应用:http://upstart.ubuntu.com
希望这可以帮助您!

4

尝试使用pm2 node模块,它更为稳定且文档详尽。这是一个用于Node.js应用程序的生产过程管理器,并内置了负载均衡器。请避免未捕获的异常问题。 https://github.com/Unitech/pm2


3
每当出现未处理的异常时,重新启动您的应用程序!如果有2000个用户使用节点Web服务器进行流视频,并且1个用户遇到异常,则重新启动不会中断所有其他用户的服务。 - Vikas Bansal
当我发现PM2这个伟大的软件时,我感到非常高兴。 - Mladen Janjetovic

1

在restify上运行得很好:

server.on('uncaughtException', function (req, res, route, err) {
  log.info('******* Begin Error *******\n%s\n*******\n%s\n******* End Error *******', route, err.stack);
  if (!res.headersSent) {
    return res.send(500, {ok: false});
  }
  res.write('\n');
  res.end();
});

1

默认情况下,Node.js通过将堆栈跟踪打印到stderr并使用代码1退出来处理此类异常,覆盖任何先前设置的process.exitCode。

了解更多

process.on('uncaughtException', (err, origin) => {
    console.log(err);
});

0

UncaughtException是“非常粗糙的机制”(确实如此),而域现在已经被弃用。然而,我们仍然需要一些机制来捕获(逻辑)域周围的错误。这个库:

https://github.com/vacuumlabs/yacol

可以帮助您完成这个任务。只需稍微多写一些代码,您就可以在整个代码中拥有良好的域语义!


0
为了避免Node.js服务器因为未处理的拒绝或未捕获的异常而崩溃,请将以下代码片段放入您的入口文件main.ts中。
process.on('unhandledRejection', (reason: string, p: Promise<any>) => {
  // I just caught an unhandled promise rejection,
  // since we already have fallback handler for unhandled errors (see below),
  // let throw and let him handle that
  throw reason;
});

process.on('uncaughtException', (error: Error) => {
  // I just received an error that was never handled, time to handle it and then decide whether a restart is needed
  errorManagement.handler.handleError(error);
  if (!errorManagement.handler.isTrustedError(error))
    process.exit(1);
});

使用UncaughtException并不是推荐的方法,根据http://nodejs.org/docs/latest/api/process.html#process_event_uncaughtexception的说明。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接