chaussen

掌握Node.js平台核心模块:进程(Process)模块——摘自RisingStack

原文链接: blog.risingstack.com

本文的主题是Node.js平台的进程(Process)模块和这个模块中一些不为人知的妙处。 读了这个贴子后,大家就会更有信心写好用于实际产品的程序了。我们将会了解Node.js平台程序的处理状态,会正常顺利地关闭程序,会更高效地处理出现的错误。

在新出的掌握Node.js平台核心模块系列贴子中,大家能学到一些核心模块所拥有的不为人知或极少有人知道的特色功能,以及如何使用这些功能。这些Node.js平台模块的基本元素经过逐条解释后,其工作原理就更好地让大家理解,并让大家知道如何排除错误。

在这一章节里,我们要讲的是Node.js平台的进程(process)模块。进程(process)模块生成的对象是一个事件触发器(EventEmitter)类的实例,作为一个全局变量,提供了当前Node.js平台进程的运行信息。

Node.js平台的进程(process)模块中需要监控的事件

因为进程(process)模块是一个事件触发器实例,像其它事件触发器实例一样,大家可以用.on函数来订阅关注其中包含的事件:

process.on('eventName', () => {
  // 处理事件
})

未捕获的异常(uncaughtException)事件

当事件循环中冒出了一个JavaScript异常却没有捕获语句时,就会发出这种事件。

默认情况下,如果处理uncaughtException事件的部分没有加入事件侦听器的话,进程就会把栈追踪信息打印到标准错误(stderr)的输出设备并退出程序。如果加了事件侦听,就能改变这种默认行为,使用侦听器里实施的方法来处理:

process.on('uncaughtException', (err) => {
  // 这里的1是一个文件描述符,代表了标准错误输出设备(STDERR)
  fs.writeSync(1, `Caught exception: ${err}\n`)
})

在过去几年里,关于这种事件我们已经看到好些错误的使用方法了。在进程(process)模块中使用uncaughtException事件时,以下几点建议我们认为是最重要的:

  • 如果uncaughtException事件发生的话,那么程序是处于一种未定义状态的;

  • 强烈建议uncaughtException事件发生后,不要将程序恢复过来,因为让程序再继续按正常方式运行是很危险的;

  • 处理这个事件的部分应该只用于同步清理(synchronous cleanup)已分配的系统资源;

  • 事件处理时抛出的异常没有捕获的话,程序就会立刻退出;

  • 应该始终用外部工具监控程序进程,需要的话就重启,比如程序崩溃。

未处理的拒绝(unhandledRejection)事件

设想以下的代码片断:

const fs = require('fs-extra')

fs.copy('/tmp/myfile', '/tmp/mynewfile')
  .then(() => console.log('success!'))

如果这里的资源文件不存在呢?那结果取决于运行的Node.js平台版本。在有些版本里,进程会运行失败,但没有任何表示,而用户就只能坐着看,不明白发生了什么。多数发生在第4版或更低版本里。

在较新的Node.js平台版本里,会得到下面的错误信息:

(node:28391) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): undefined
(node:28391) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js平台process with a non-zero exit code.

这段话的意思是虽然我们利用承诺(Promise)对象来复制文件,但Promise对象发生错误时,却缺少了错误处理部分。 上述例子原本应该这样写:

fs.copy('/tmp/myfile', '/tmp/mynewfile')
  .then(() => console.log('success!'))
  .catch(err => console.error(err))

这里的问题和uncaughtException事件一样,拒绝访问Promise对象时,没有办法处理这个事件。Node.js平台进程会处于一种未知状态。更糟糕的是这可能造成文件描述符失败,以及内存泄漏。 在这种情形里,最好的措施是重启Node.js平台进程。

要做到这一点,就应该给unhandledRejection事件接上一个事件侦听器,错误发生时用process.exit(1)命令来退出进程。

这里,我们建议使用Matteo Collina的make-promises-safe(让Promise对象更安全)软件包,可以很好地解决这个问题。

Node.js信号事件

Signal events will also be emitted when Node.js收到简便操作系统(POSIX)的信号事件时也会触发自己的信号事件。让我们看看其中最重要的两个信息SIGTERMSIGUSR1吧。

要想知道所有支持的信号,可以看这里

终止(SIGTERM)信号

SIGTERM信号发到Node.js平台进程是为了请求进程终止。 与结束(SIGKILL)信号不同,进程可以侦听或无视这个信号。

这个信号让进程关闭时能释放分配给进程的资源,如文件句柄和数据库连接等,使关闭方式更妥善。这种关闭程序的方式就称为正常关闭(graceful shutdown)

基本上,进行正常关闭需要以下几个步骤:

  1. 程序收到要求停止的通知,即收到SIGTERM信号。

  2. 程序通知负载平衡器不能再处理新的请求了。

  3. 程序结束所有正在处理的请求。

  4. 然后,程序进程正确释放所有的资源,如数据库连接等。

  5. 程序退出,放出"成功(success)"状态码(process.exit())

看一下这篇文章,有更多关于Node.js平台正常关闭的内容。

用户(SIGUSR1)信号

以POSIX的标准来说,用户能自己设定条件发送SIGUSR1信号和SIGUSR2`信号。Node.js平台启动内置的调试器就是利用了这种事件。

运行以下命令可以发送SIGUSR1信号给进程:

`kill -USR1 PID_OF_THE_NODE_JS_PROCESS`

命令运行后,收到信号的Node.js平台进程就会显示调试器正在运行:

Starting debugger agent.
Debugger listening on [::]:5858

Node.js进程(process)模块对外公开的方法与变量

process.cwd()方法

这个方法返回目前正在运行Node.js平台进程的工作目录。

$ node -e 'console.log(`Current directory: ${process.cwd()}`)'
Current directory: /Users/gergelyke/Development/risingstack/risingsite_v2

如果要更改目录,可以调用process.chdir(path)方法。

process.env变量

进程的这个属性返回的是一个对象,里面包括了用户环境变量,就像environ命令一样。

我们构建程序要符合”12要素程序原则(12-factor application principles)“。只有大量依赖这个变量,才能这些原则。正像12要素程序第三原则说的那样,必须把所有的配置信息都储存在用户环境变量里。

为什么要多用环境变量?因为在每次部署程序之前,不用修改代码就能很容易地修改那些变量。不小心把存储在环境变量里的配置记录到代码库里去的机会很小,这和配置文件不同。

值得一提的是,process.env对象里的值可以修改,但不会体现在用户环境变量里。

process.exit([code])方法

这个方法告诉Node.js平台进程要同步终止这个进程,并附上一个退出的状态码。调用这个方法的后果主要有以下几点:

  • 会强制进程尽快退出,

    • 即使还有些异步操作在进行

    • 因为把结果写入标准输出STDOUT与标准错误STDERR是一个异步操作,所以有些日志信息会丢失

  • 多数情况下,不建议使用process.exit()方法退出,而应该使事件循环完结来关闭程序。

process.kill(pid, [signal])方法

用这个方法可以把任何POSIX信号发送给任何进程。如名字所示,这个方法不但终结进程,而且还可以发送信息,就像调用系统的kill命令一样。

Node.js平台使用的退出状态码

如果一切顺利,Node.js平台退出程序的状态码是0。但如果进程由于错误而退出,就会得到下面错误码之一:

  • 1:未捕获的致命异常,uncaughtException的处理部分没能处理;

  • 5:V8引擎发生致命错误,比如内存分配失败;

  • 9:无效参数,使用的选项未知,或选项需要一个值,而这个值没有设定。

_这些仅仅是最常见的退出状态码。要了解所有的退出状态码,请参见https://nodejs.org/api/process.html#process_exit_codes。_

更多了解Node.js平台

这些是使用Node.js平台进程模块时最重要的方面。我们希望大家都遵循上面列出的建议,充分利用Node.js平台。如遇到问题,尽请在下面的评论区告诉我们。

学习了核心模块,就能迅速掌握Node.js平台的窍门了! 不过,如果感觉要用到的基础知识可能还不止这些,或者对自己的组织如何实施Node.js平台还抱有疑问,我们可以帮忙!