本文由云+社区发布作者:曹晓晖使用Node.js搭建HTTPServer是家常便饭。在生产环境中,Node进程能否顺利重启直接关系到服务的可靠性,其重要性不容忽视。既然是平滑重启,就涉及到新旧进程的接续过渡:一是保证新进程的顺利进入,二是保证旧进程的顺利退出。本文主要讨论如何保证旧进程在过渡期间的顺利退出。那么什么样的退出才是顺利的呢?如何定义顺利退出?以流程出口为时间划分点,我们可以将请求分为两种:增量请求和存量请求。进程离开前,停止接收新的(增量)请求进程离开前,保证未完成(存量)的请求正常响应所以,要达到以上两个目标,我们基本认为进程是顺利离开的。在讲如何让进程顺利离开之前,我们需要一个机制,让我们在进程离开的时候能够主动通知。这就涉及到进程间通信(IPC)的知识。让我们简单了解一下。进程间通信对于Unix或类Unix系统,进程间通信有多种方式——信号(Signal)就是其中之一。信号有很多种,例如SIGINT、SIGTERM和SIGKILL。这些信号根据特定需求用于不同的场景。比如SIGKILL,一般用于强行杀掉进程。我们可以在命令行执行kill-l查看所有信号,如下图(里面的数字表示信号编号):$kill-l1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SIGTRAP6)SIGABRT7)SIGEMT8)SIGFPE9)SIGKILL10)SIGBUS11)SIGSEGV12)SIGSYS13)SIGPIPE14)SIGALRM15)SIGTERM16)SIGURG17)SIGSTOP18)SIGTSTP19)SIGCONT20)SIGCHLD21)SIGTTINIG22)SIGTTOU22)SIGTTOU2Z)SIGTTOU22Z))SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGINFO30)SIGUSR131)SIGUSR2我们可以使用kill命令向进程发送指定信号:#向进程发送SIGTERM信号(默认,无需指定信号类型)$kill#向进程发送SIGINT信号,其中是具体的进程ID$kill-INT#向进程发送SIGKILL信号$kill-KILL#或者$kill-9进程可以响应接收到的信号Responded。对于Node应用程序,信号作为事件发送到Node进程,进程接收带有默认回调的SIGTERM和SIGINT事件,如官方文档所述:'SIGTERM'和'SIGINT'在非Windows平台上具有默认处理程序,可重置使用代码128+信号编号退出前的终端模式。如果这些信号之一安装了侦听器,其默认行为将被删除(Node.js将不再退出)。这句话很抽象,它是什么意思呢?我们以一个简单的Node应用程序为例。新建一个文件,输入以下代码,保存为server.js:consthttp=require('http');constserver=http.createServer((req,res)=>{setTimeout(()=>{res.writeHead(200,{'Content-Type':'text/plain'});res.end('Itworks');},5000);});server.listen(9420);这里为了测试方便,应用程序每收到一个http请求,等待5秒再响应。执行nodeserver.js以启动应用程序。为了向应用程序发送信号,我们需要获取应用程序的进程ID,我们可以使用lsof命令查看:$lsof-iTCP:9420COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAMEnode70826myunlessor13uIPv60xd250033eef8912eb0t0TCP*:9420(LISTEN)其实我们也可以通过代码中的console.log(process.pid)获取进程ID。这里只是在知道监听TCP端口的情况下获取进程的方法。然后,我们发出请求,直到我们得到响应(等待5秒),我们向应用程序发送一个SIGINT信号。$curlhttp://localhost:9420&$kill-INT70826curl:(52)Emptyreplyfromserver[1]+Exit52curlhttp://localhost:9420可以看到请求未能收到正常响应。也就是说,默认情况下,当Node应用程序收到SIGINT信号后,会立即杀掉进程,忽略进程尚未处理的请求。幸运的是,我们可以手动监听进程的SIGINT事件,像这样:process.on('SIGINT',()=>{//dosomethinghere});如果我们在事件回调中什么都不做,那就意味着忽略这个信号,进程该怎么做,就好像什么都没发生一样。那么如果我手动监听SIGKILL会发生什么?抱歉,无法监控SIGKILL。官方文档说:'SIGKILL'不能安装监听器,它会在所有平台上无条件终止Node.js。这是有道理的。要知道SIGKILL是用来杀进程的。你不能干涉它的行为。回到上面的问题,我们可以大致理解为Node应用响应SIGINT事件的默认回调是这样的:process.on('SIGINT',()=>{process.exit(128+2/*signal数字*/);});我们可以打印退出代码来验证:$nodeserver.js$echo$?130有了信号,我们就可以在进程离站时主动通知它了。下面说说进程是如何顺利退出的。如何让进程顺利退出基于上面的例子,即在文件server.js中,添加如下代码:process.on('SIGINT',()=>{server.close(err=>{process.exit(err?1:0);});});这段代码很简单,我们重写应用接收SIGINT事件的默认行为,不是简单粗暴的直接杀掉进程,而是在server.close方法的回调中调用process.exit方法,然后继续实验。$lsof-iTCP:9420COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAMEnode75842myunlessor13uIPv60xd250033ec7c9362b0t0TCP*:9420(LISTEN)$curlhttp://localhost:9420&[1]75878$kill4-2$758有效[1]+Donecurlhttp://localhost:9420可以看到应用在退出前(即进程离开站点前)成功响应了库存请求。我们还可以验证在进程离开之前,它没有收到增量请求:$curlhttp://127.0.0.1:9420curl:(7)Failedtoconnectto127.0.0.1port9420:Connectionrefused这是服务器。close做什么,进程的顺利退出就是这么简单。官方文档是这样描述这个API的:Stopstheserverfromacceptingnewconnectionsandkeepsexistingconnections。此函数是异步的,当所有连接结束并且服务器发出“关闭”事件时,服务器最终关闭。一旦“关闭”事件发生,将调用可选的回调。与该事件不同,如果服务器在关闭时未打开,它将以错误作为唯一参数调用。结束语进程优雅退出只是Node进程优雅重启的一部分。在生产环境中,新旧流程的更换涉及到流程负载均衡、流程生命周期管理等方面的考虑。专业的工具做专业的事,PM2是Node进程管理的好选择。本文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们的腾讯云技术社区-云家社区公众号和知乎代理号