V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OneAPM
V2EX  ›  Node.js

NodeJS 如何异常处理 uncaughtException

  •  9
     
  •   OneAPM · 2015-06-09 16:22:02 +08:00 · 3111 次点击
    这是一个创建于 3245 天前的主题,其中的信息可能已经有所发展或是发生改变。

    很多 NodeJS 的开发者在抱怨异常处理太麻烦,我们会通过一些列博客梳理一下NodeJS中常见的异常处理的手段。
    和大多数编程语言一样,在 NodeJS 里可以通过throw抛出一个异常:

    throw new Error('Catch me');

    为了捕获这个异常需要把代码包在Try Catch中:

    try{
        throw new Error('Catch me');
    }catch(e){
        // error captured
    }
    

    然而,由于 NodeJS 的异步特性,上述代码只需稍加改造就会失效:

    try{
        process.nextTick(function my_app(){
            throw new Error('Catch me');
        })
    }catch(e){
        // never called
    }
    

    在现实世界里,异常总是会产生在某个模块中。所谓模块就是能完成一个功能的单元,即使是一个简单的函数也可以被看做一个模块。随着项目代码行数增多,异步嵌套的复杂性加强,经常会有异常没捕获的情况发生。一个没有很强健壮性的 NodeJS 应用,会因为一个未捕获的异常就整个挂掉,导致服务不可用。要改变大家觉得NodeJS是脆弱的这个认识,需要开发者加深对这门语言异常处理机制的了解。

    uncaughtException

    uncaughtException 其实是 NodeJS 进程的一个事件。如果进程里产生了一个异常而没有被任何Try Catch捕获会触发这个事件。为了简化问题,我们还是先看看同步情况下的例子。

    function external() {
      throw new Error('Catch me');
     }
    
    function internal() {
      external();
    }
    
    internal(); //error will be thrown
    

    在命令行里执行这个程序,脚本会在抛出异常的那一行中断。接下来,由于没有Try Catch,异常会一直冒泡直到事件循环为止,而NodeJS对异常的默认处理非常简单,处理的代码 类似 于:

    function _MyFatalException(err){
        if(!process.emit('uncaughtException',err)){
            console.error(err.stack);
            process.emit('exit',1);
          }
    }
    

    NodeJS对于未捕获异常的默认处理是: - 触发 uncaughtException 事件 - 如果 uncaughtException 没有被监听,那么 - 打印异常的堆栈信息 - 触发进程的 exit 事件

    如果你正在用 NodeJS 开发服务器,那么你肯定不希望偶然的一个异常让整个服务器挂掉。那么是不是只要监听了 uncaughtException 就可以阻止服务器的进程退出呢? 答案是可以,但是不要这么做!。看这个例子:

    var express = require('express');
    
    function external(cb) {
    process.nextTick(function () {
        throw new Error();
        cb.call(null, 'sunny');
    })
    }
    
    var app = express();
    app.get('/weather', function (req, res) {
        external(function (data) {
            res.end('Weather of Beijing is ' + data);
    })
    })
    app.listen(8018);
    
    function noop(){}
    process.on('uncaughtException', noop)
    

    上面这个例子假设用户访问站点的时候可以看到当地的天气,我们用 apache2-utils 来模拟请求

    ab -n 1000 -c 20 http://localhost:8018/weather

    糟糕!请求一直在等待,内存上涨。原因在于res.end 永远不会执行,现有的I/O处于等待的状态,已经开辟的资源不仅不会被释放,而且服务器还在不知疲倦地接受新的用户请求。

    在 NodeJS 中处理异常是代价高昂的,而且一不小心就会导致内存泄露和让应用程序处于不稳定的状态。为了提高健壮性,我们可以用Cluster模式,由之而来的推荐做法是: - 针对发生异常的请求返回一个错误代码 - 出错的Worker不再接受新的请求 - 退出关闭Worker进程


    本文系OneAPM工程师编译整理,想阅读更多技术文章,请访问OneAPM官方技术博客

    4 条回复    2015-07-15 16:12:35 +08:00
    YuJianrong
        1
    YuJianrong  
       2015-06-10 14:55:53 +08:00
    文章不错不过读起来太伤眼……
    OneAPM
        2
    OneAPM  
    OP
       2015-06-10 15:38:17 +08:00
    @YuJianrong 下次我们改进下排版 :P
    jiangzhuo
        3
    jiangzhuo  
       2015-06-24 14:47:17 +08:00
    怎麼不提Domain呢
    adoyle
        4
    adoyle  
       2015-07-15 16:12:35 +08:00
    问一个问题,当服务端发生 uncaughtException,而且是因为请求某些路由而必定发生的,有下面两种处理方式,你会选哪个?

    1. 记录异常,通知管理员,不退出进程。让服务端继续跑,尽可能多的响应其他请求。

    2. 记录异常,通知管理员,重启进程。但是只要触发到异常就会立刻重启,有可能很频繁,会导致服务端一段时间内无法响应请求。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   941 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 19:40 · PVG 03:40 · LAX 12:40 · JFK 15:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.