Node.js 为什么进程没有 exit?

不知道各位 noder 们有没有碰到过这样一个疑问,当你写的 Node.js 代码是异步逻辑的时候,我们要怎么才能知道 Node.js 进程是什么,什么时候才会退出呢?Node.js 又是怎么知道异步执行结束了?或者当你执行一段 Node.js 代码之后,进程去一直等在这里没有退出又是怎么回事呢?

setTimeout(() => {
   console.log('hello');}, 10000);

比如我们看到以上的代码,会发现这个进程会等待 10s 之后才会输出一个 hello 然后退出。

实际上Node.js 会注意追踪所有异步请求的进展,当我们使用文件异步读写、socket 读写、定时器等异步操作时,所有的异步请求都会维持在 Node.js 的事件队列中。这里有很多常见的异步请求:

  • http 请求、数据库请求等 IO 请求操作
  • net.Server.listen() 或者 http.Server.listen() 等端口监听
  • fs.write() 类型的文件 IO 操作
  • console.log() 输出日志
  • setTimeout()、setInterval() 等定时器操作
  • process.send() 等异步请求发送

等等,只有当所有的这些异步操作都结束的时候,Node.js 的进程才会退出。如果不了解这个情况的话,可能用户会直接使用 process.exit() 来退出进程,这个方式过于简单粗暴在某些边界情况下可能会造成一些麻烦或者损失。

实际上,随着 Node.js 在国内各个大厂的日渐纯熟的运用下,Node.js 不可能避免的也要接入各个运维体系之中。而在运维体系下,有很多常见的操作,比如单节点的 start、stop、restart。而这中间的 stop 和 restart 操作过程中,Node.js 进程的退出实际上是需要像传统服务端的运维方案靠近的。

常见的是 signal 的方式(比如大家熟悉的 kill -9)来对进程进行运维操作。但本文要讨论并不是 kill -9 这样比较粗暴的退出方式,而是运维过程中更常见的 kill -15 (软退出),这种情况下不论一个进程是由什么语言都应该注意需要处理和关闭好各项资源以及请求然后来优雅的退出进程。

优雅退出主要针对的是:

  • 此时进程不应该继续对外提供服务了,比如 Node.js 中的 http, net 等 listen 状态的 server 应该 close 了,否则此时有请求进来,可能执行到一半进程就直接 exit 导致提供了不可用的服务。
  • 有些数据库的锁、共享内存等公共资源需要释放。如果没有注意释放可能会有一些未期望/未定义的边缘 case 出现。
  • 常规的运维过程中输出各项自检/调试的日志(直接 process.exit() 可能啥记录都没有了)

了解了一些运维场景下,对进程退出的一些要求之后,我们最后再来看一个情况,也就是说如果你想主动的优雅的,close 掉各项 server 或者回收各项资源的情况下,为什么 Node.js 进程没有自然而然的退出掉?

上文中,我们举得例子十分简单,但实际项目中可能存在着大量的异步逻辑,某项漏掉的项可能会有一些没有还没结束的异步请求是我们需要去等待,不要粗暴退出的,而另外某些有些没有意义的定时器则确实可以直接忽略,在这样的复杂情况下我们如果去排查到底有哪些异步请求还在 pending,哪些又是业务可以忽略的的异步呢?

这里笔者推荐大家两个办法,一个是通过 Node.js 内置的两个方法去获取正在 pending 进程的一些信息:

process._getActiveHandles()
process._getActiveRequests()

这个是原生支持的检查方法,大家可以在 Node.js 官方的 issue 中看到相关的讨论(https://github.com/nodejs/node-v0.x-archive/issues/1025#issuecomment-10672235)。

不过这个方法获取的日志不是那么直观,这里不做太多介绍。与之相对的是,另外一个推荐方案,使用一个可以直观检查 “为什么 Node.js 还在运行” 的库来专门检查一下:

  1. 运行 npm install -D why-is-node-running 来安装这个依赖。
  2. 添加 const analysisLog = require('why-is-node-running'); 到你的代码入口。
  3. 最后在你结束所有关闭操作,但是进程还没有推出的时候运行:
afterAll(async () => {  analysisLog();}

通过以上方式,你可以获得一个详细的追踪信息,里面会列出所有出于 pending 状态的异步操作的代码位置,例如:

There are 5 handle(s) keeping the process running
# Timeout/xxx/node_modules/why-is-node-running/example.js:6  - setInterval(function () {}, 1000)/xxx/node_modules/why-is-node-running/example.js:10 - createServer()
# TCPSERVERWRAP/xxx/node_modules/why-is-node-running/example.js:7  - server.listen(0)/xxx/node_modules/why-is-node-running/example.js:10 - createServer()

通过这些信息,你可以排查到有哪些异步操作/请求是你准备优雅退出时还没有处理,从而导致你的进程没有自然退出的。

不过需要注意的是,这个库的实现原理,是通过 Node.js 8.x 中引入的 async hooks 这个新特性注册了全局的异步监听器,把所有的异步请求的类型都记录过异步汇总整理的,所以仅建议在开发和调试环境使用,不推荐在线上环境使用这个工具来排查。

小结

  • Node.js 的进程退出会等待异步处理完成
  • 常见的运维过程中会碰到需要进程优雅退出的场景,而 Node.js 自然退出是最好的,process.exit 是比较粗暴的
  • Node.js 开发者可以使用排查工具来排查哪些因素阻碍了进程自然退出。

进程相关阅读推荐

文章转载自公众号 “Node地下铁”

本文分享自微信公众号 - Nodejs技术栈(NodejsDeveloper)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏呼延

Concurrent Hash Map源码阅读参考链接

大家都知道HashMap是线程不安全的,想要在并发的环境中使用,用什么呢?HashTable?采用syncgronized加锁,导致效率及其底下.在java5之...

9830
来自专栏dylanliu

124. Binary Tree Maximum Path Sum

Given a non-empty binary tree, find the maximum path sum.

8240
来自专栏谭广健的专栏

简单就是美,论jqgrid 导出的反射美

上一次写原创都已经3个月前,由于最近换了新的环境;认识了新的人、新的朋友。也学到了一些新的技能如安卓控制开发,Iot物联网的流程控制MQTT传输等。。好...

11510
来自专栏dylanliu

AbstractQueuedSynchronizer 源码分析(共享锁)

在AbstractQueuedSynchronizer中使用LockSupport类来实现线程的挂起和唤醒,对应方法分别我park和unpark,内部实现原理是...

13940
来自专栏呼延

使用@async注解实现异步调用

异步调用对应的是同步调用,假设现在有三个无关任务等待执行,同步调用的方式是逐次等待,即第一个任务完成后再开始第二个任务….以此类推。

34930
来自专栏dylanliu

LeetCode23-Merge k Sorted Lists

Merge k sorted linked lists and return it as one sorted list. Analyze and descri...

12730
来自专栏世民谈云计算

理解OpenShift(2):网络之 DNS(域名服务)

在Linux 系统上,当一个应用通过域名连接远端主机时,DNS 解析会通过系统调用来进行,比如 getaddrinfo()。和任何Linux 操作系统一样,Po...

14010
来自专栏木二天空

003.Ceph扩展集群

需求:添加Ceph元数据服务器node1。然后添加Ceph Monitor和Ceph Manager node2,node3以提高可靠性和可用性。

7920
来自专栏微信公众号:Java团长

当面试官问我Mybatis初始化原理时,我笑了

来源:blog.csdn.net/luanlouis/article/details/37744073

10730
来自专栏世民谈云计算

在Kubernetes中利用 kubevirt 以容器方式运行虚拟机

随着Docker和Kubernetes生态圈的发展,云计算领域对容器的兴趣达到了狂热的程度。 容器技术为应用程序提供了隔离的运行空间,每个容器内都包含一个独享的...

1.3K20

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励