.|└── chrome(1)├── gpu-process(2)└── zygote(3)└── renderer(4)
在图片生成完成时 , chrome 主进程退出 , 剩下的三个孤儿僵尸进程被托管到顶层 npm 进程下 , 但是 npm 进程无力回收 , 所有每生成一次图片便会新增三个僵尸进程 。在成千上万次图片生成以后 , 系统中就充满了僵尸进程 。
解决办法为了解决这个问题 , 不能让 node/npm 成为 init 进程 , 让有能力接管僵尸进程的服务成为 init 进程即可 , 有两个解决办法 。
- 使用 bash 启动 node 或者 npm
- 增加专门的 init 进程 , 比如 tini
ADD test.tar.gz .# CMD ["npm", "run", "start"]CMD ["/bin/bash", "-c", "set -e && npm run start"]
使用这种方式是比较简单 , 而且之前线上没有出问题正是因为一开始是使用这种 bash 方式启动 node , 后面有一个小兄弟为了统一启动命令将这个命令改为 npm run start , 问题才出现的 。但使用 bash 并非完美的方案 , 它有一个比较严重的问题 , bash 不会传递信号给它启动的进程 , 优雅停机等功能无法实现 。
接下来做一个实验 , 验证 bash 不会传递信号给子进程的说法 , 新建一个 signal_test.c 文件 , 它处理 SIGQUIT、SIGTERM、SIGTERM 三个信号 , 内容如下 。
#include <signal.h>#include <stdio.h>static void signal_handler(int signal_no) {if (signal_no == SIGQUIT) {printf("quit signal receive: %dn", signal_no);} else if (signal_no == SIGTERM) {printf("term signal receive: %dn", signal_no);} else if (signal_no == SIGTERM) {printf("interrupt signal receive: %dn", signal_no);}}int main() {printf("in mainn");signal(SIGQUIT, signal_handler);signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);getchar();}
在我 Centos 和 Mac 上运行这个 signal_test 程序时 , 发送 kill -2、-3、-15 给这个程序 , 都会有对应的打印输出 , 表示收到了信号 。如下所示 。kill -15 47120term signal receive: 15kill -3 47120quit signal receive: 3kill -2 47120interrupt signal receive: 2
在 Docker 镜像中使用 bash 启动这个程序时 , 发送 kill 命令给 bash 以后 , bash 并不会将信号传递给 signal_test 程序 。在执行 docker stop 以后 , docker 会发送 SIGTERM(15) 信号给 bash , bash 并不会将这个信号传递给启动的应用程序 , 只能等一段时间超时 , docker 会发送 kill -9 强制杀死这个 docker 进程 , 无法达到优雅停机的功能 。于是有了下面的第二种解决方案 。
解决方式二:使用专门的 init 进程Node.js 提供了两种方案 , 第一种是使用 docker 官方的轻量级 init 系统 , 如下所示 。
docker run -it --init you_docker_image_id
这种启动方式会以 /sbin/docker-init 为 PID 为 1 的 init 进程 , 不会把 Dockerfile 中 CMD 作为第一个启动进程 。以下面的 Dockerfile 内容为例
...CMD ["./signal_test"]...
执行 docker run -it --init image_id 启动 docker 镜像 , 此时镜像内的进程如下所示 。UIDPIDPPIDC STIME TTYTIME CMDroot100 15:30 pts/000:00:00 /sbin/docker-init -- /app/node-defaultroot610 15:30 pts/000:00:00 ./signal_test
可以看到 signal_test 程序作为 docker-init 的子进程启动了 。在 docker stop 命令发送 SIGTERM 信号给镜像以后 , docker-init 进程会将这个信号转给 signal_test , 这个应用进程就可以收到 SIGTERM 信号做自定义的处理 , 比如优雅停机等 。
除了 docker 的官方方案 , Node.js 的最佳实践还推荐了一个 tini 这样一个 C 语言写的极小的 init 进程 , github.com/krallin/tin…。它的代码较短 , 很值得一读 , 对理解信号传递、处理僵尸进程非常有帮助 。
小结通过这篇文章 , 希望你可以搞懂僵尸进程、孤儿进程、PID 为 1 的进程是什么 , 以及为什么 node/npm 不适合做 PID 为 1 的进程 , bash 作为 PID 为 1 的进程有什么缺陷 。
下面留一个作业题 , 考考你对进程 fork 函数的理解 。如下程序连续调用三次 fork() 调用后会产生多少新进程?
推荐阅读
- 防冻液多久换一次?别等到发动机出故障后才知道
- 礼仪微课五分钟 如何上好一堂礼仪课
- 纱窗几年换一次,纱窗的使用寿命是多少年
- 空调管路杀菌怎么操作,空调管路杀菌多久一次
- 福建名菜佛跳墙,原来做法这么简单,汤汁醇厚,吃一次就念念不忘
- 使用docker部署golang服务
- 储水式电热水器怎么清洗,储水式电热水器多久保养一次
- 什么是Docker?与虚拟机有什么区别?
- |第一次当领导,如何说话才显得有威信?
- 推荐5款好用的开源 Docker 工具