这句话写的很抽象,它是什么意思呢?我们以一个简单的 Node 应用为例。
新建文件,键入如下代码,将其保存为 server.js:
const http = require('http'); const server = http.createServer((req, res) => { setTimeout(() => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('It works'); }, 5000); }); server.listen(9420);
这里为了方便测试,对应用接收到的每个 http 请求,等待 5 秒后再进行响应。
执行 node server.js
启动应用。为了给应用发送信号,我们需要获取应用的进程 ID,我们可以使用 lsof 命令查看:
$ lsof -i TCP:9420 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 70826 myunlessor 13u IPv6 0xd250033eef8912eb 0t0 TCP *:9420 (LISTEN)
事实上,我们也可以在代码里通过 console.log(process.pid) 获取进程 ID。这里只是顺便介绍一种,在知道监听 TCP 端口的情况获取进程的方式。
随后,我们发起一个请求,在收到响应之前(有 5 秒等待时间),我们给应用发送 SIGINT 信号。
$ curl http://localhost:9420 & $ kill -INT 70826 curl: (52) Empty reply from server [1]+ Exit 52 curl http://localhost:9420
可以看到,请求没能正常收到响应。也就是说,默认情况下,Node 应用在接收到 SIGINT 信号时,会马上把进程杀死,无视进程还没处理完成的请求。所幸的是,我们可以手动监听进程的 SIGINT 事件,像这样:
process.on('SIGINT', () => { // do something here });
如果我们在事件回调里什么都不做,就意味着忽略该信号,进程该干嘛干嘛,像什么事情都没发生一样。
那么,如果我手动监听 SIGKILL 会如何呢?对不起,SIGKILL 是不能被监听的,官方文档如是说:
'SIGKILL' cannot have a listener installed, it will unconditionally terminate Node.js on all platforms.
这是合情合理的,要知道 SIGKILL 是用于强杀进程的,你无法干预它的行为。
回到上面的问题,我们可以近似地理解为 Node 应用响应 SIGINT 事件的默认回调是这样子的:
process.on('SIGINT', () => { process.exit(128 + 2/* signal number */); });
我们可以打印 exit code 来验证:
$ node server.js $ echo $? 130
有了信号,我们就能主动通知进程何时离场了,下面谈一谈进程如何平滑离场。
如何让进程平滑离场
我们在上面示例基础上,也就是在文件 server.js 中,补充如下代码:
process.on('SIGINT', () => { server.close(err => { process.exit(err ? 1 : 0); }); });
这段代码很简单,我们改写应用接收到 SIGINT
事件的默认行为,不再简单粗暴直接杀死进程,而是在 server.close
方法回调中再调用 process.exit
方法,接着继续试验一下。
$ lsof -i TCP:9420 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 75842 myunlessor 13u IPv6 0xd250033ec7c9362b 0t0 TCP *:9420 (LISTEN) $ curl http://localhost:9420 & [1] 75878 $ kill -2 75842 $ It works [1]+ Done curl http://localhost:9420
可以看到,应用在退出前(即进程离场前),成功地响应了存量
请求。
我们还可以验证,进程离场前,确实不再接收增量
请求:
$ curl http://127.0.0.1:9420 curl: (7) Failed to connect to 127.0.0.1 port 9420: Connection refused
这正是 server.close 所做的事,进程平滑离场就是这么简单,官方文档是这么描述这个 API 的:
Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. Unlike that event, it will be called with an Error as its only argument if the server was not open when it was closed.
结束语
进程平滑离场只是 Node 进程平滑重启的一部分。生产环境中,新旧进程的接替涉及进程负载均衡、进程生命周期管理等方方面面的考虑。专业的工具做专业的事,PM2 就是 Node 进程管理很好的选择。