美文网首页
在NodeJS中使用domain模块捕获异步异常

在NodeJS中使用domain模块捕获异步异常

作者: youthcity | 来源:发表于2018-10-14 22:43 被阅读372次

    背景

    最近,线上有一个重要服务发生了宕机,导致上课中的教师大面积的断线。我们通过错误日志,查到一个 socket hang up 报错。但是无法根据错误定位到异常的代码。通过查询访问日志,也无法定位到宕机前发生错误的接口。我们期望能在代码里对异常进行捕获,这样不会导致服务挂掉,还希望获取异常的上下文,以便定位错误。

    events.js:183
          throw er; // Unhandled 'error' event
          ^
    
    Error: socket hang up
        at TLSSocket.onHangUp (_tls_wrap.js:1137:19)
        at Object.onceWrapper (events.js:313:30)
        at emitNone (events.js:111:20)
        at TLSSocket.emit (events.js:208:7)
        at endReadableNT (_stream_readable.js:1064:12)
        at _combinedTickCallback (internal/process/next_tick.js:138:11)
        at process._tickCallback (internal/process/next_tick.js:180:9)
    npm ERR! code ELIFECYCLE
    npm ERR! errno 1
    npm ERR! lesson@1.0.0 start: `node ./app/bin/www;`
    npm ERR! Exit status 1
    npm ERR!
    npm ERR! Failed at the lesson@1.0.0 start script.
    npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
    
    npm ERR! A complete log of this run can be found in:
    npm ERR!     /root/.npm/_logs/2018-09-30T08_22_32_703Z-debug.log
    

    使用uncaughtException捕获异步异常

    首先,我们想到了 process上的 uncaughtException 事件。只要给 process 的uncaughtException事件注册了回调,服务就不会异常退出。看起来十分美好,起码我们的服务不会挂掉了。但是,带来了其他问题。

    uncaughtException 事件

    uncaughtException,是NodeJS进程(Process)中的一个事件。如果进程中发生了一个异常且异常没有被任何try...catch进行捕获就会触发这个事件。

    下面以同步异常为例,介绍一下NodeJS对异常的默认处理。

    process.on('uncaughtException', (error) => {
      console.log('call uncaughtException handle');
    });
    
    // 执行一个不存在的异常
    nonexistentFunc();
    

    首先,我们注册process的uncaughtException事件,然后执行了一个不存在的函数。当执行这段程序时,程序会因为函数nonexistentFunc不存在,而抛出异常。由于没有进行try...catch...处理,异常将会冒泡直到事件循环为止。NodeJS对于异常的默认处理,类似代码如下:

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

    NodeJS对于异常的默认处理的顺序是,首先触发 uncaughtException事件,若事件没有被监听到,则打印堆栈的错误信息,最后调用进程的退出事件。若事件监听到,则处理uncaughtException的注册回调。

    uncaughtException 事件发生的条件

    1. 当程序发生了异常
    2. 且异常未被 try...catch捕获

    缺点与问题

    1. 无法获取异常的上下文。
    2. 无法给出友好的异常处理。例如,当接口发生 uncaughtException 时,无法获取到 response对象(已经丢失了上下文),告知调用方服务当前出现异常。
    3. 会导致内存泄露。uncaughtException事件发生后,会丢失当前环境的堆栈,可能导致Node不能正常进行内存回收,从而导致内存泄露。

    由于使用 uncaughtException捕获异常,会导致内存泄露。建议的使用方式为,当 uncaughtException事件发生时,记录error,然后结束 Node 进程进行重启服务。(😭然后我们在项目中并没有这样做。。。业务不允许重启。。)

    使用domain模块捕获异步异常

    注意:domain模块将被弃用。

    domain 模块,简化了异常的处理方式,可以处理try...catch..无法捕获的异常。且不会丢失上下文,也不会导致程序退出。

    我们老项目使用的是 express 框架,因此可以使用中间件的方式,处理请求中的异步异常。代码如下:

    const domain = require('domain');
    
    app.use((req, res, next) => {
      const req_domain = domain.create();
    
      req_domain.on('error', (err) => {
        console.log(err);  // 打印错误日志
        res.send(500, err.stack);
      });
    
      req_domain.run(next);
    });
    

    在中间件中,首先创建一个 domain对象,然后注册 error 事件的回调,最后在创建域的上下文中执行next函数。

    什么时候触发domain的error事件:
    进程抛出了异常,没有被任何的try catch捕获到,这时候将会触发整个process的processFatal,此时如果在domain包裹之中,将会在domain上触发error事件,反之,将会在process上触发uncaughtException事件。

    如果想要了解更多关于 domain 模块,可以阅读这篇文章——《Node.js 异步异常的处理与domain模块解析》

    优化方案

    domain虽然很好用,但不是万能的。有时,也无法捕获部分异常。为了保证服务不会挂掉。我可以使用 uncaughtException事件进行兜底。

    基础版

    domain + uncaughtException

    domain 来捕获大部分异常和合理处理异常退出,uncaughtException 来避免服务挂掉。

    升级版

    domain + uncaughtException + Cluster

    我们可以使用Cluster模块,开启多个工作线程。当遇到domain无法捕获的异常,在 uncaughtException 捕获到时,为了避免内存泄露,我们可以结束当前work进程。具体实现如下:

    const cluster = require('cluster');
    
    process.on('uncaughtException', (err) => {
    
      console.log(err); // 将异常写入日志
    
      server.close(); // 关闭服务连接
    
      // 通知 master 进程停止服务
      if (cluster.worker) {
          cluster.worker.disconnect();
      }
    
    });
    

    总结

    尽管domain模块即将废弃,但是我们仍然可以使用它,发现和定位线上异常。

    参考资料

    相关文章

      网友评论

          本文标题:在NodeJS中使用domain模块捕获异步异常

          本文链接:https://www.haomeiwen.com/subject/xapaaftx.html