背景
之前听过这个工具,但是没用过它,只知道它是一个进程管理的工具。然后最近我在公司要用到这个东西来部署
Swoole Server
服务,并且在使用它的时候,遇到了端口占用的问题,经过同事指点,说是supervisor
不能够用来管理守护进程,而我的Swoole Server
配置了守护进程。于是我对这个工具的工作原理产生了兴趣。
我们先来感受一下supervisor
的工作原理。首先,我们来写一份配置:
1 | [root@e2a14c00e7f6 ~]# cat /etc/supervisord.d/cat.ini |
这里,我打算起一个cat
命令进程。
然后,我们来启动服务:
1 | [root@e2a14c00e7f6 ~]# supervisorctl restart cat |
这个时候,我们来看一看我们的进程:
1 | [root@e2a14c00e7f6 ~]# ps -ef |
我们发现,这里有一个supervisord
进程,根据名字后面有一个d
,我们可以很容易猜到,这应该是一个守护进程。然后,我们发现cat
进程他的父进程是153
,这正好是supervisord
进程的pid
。所以,我们可以大概猜测,supervisord
是通过监听SIGCHLD
来实现进程重启的。我们来验证下。
首先,我们查看一下supervisord
进程的系统调用:
1 | [root@e2a14c00e7f6 ~]# strace -p 153 |
我们来给cat
进程发送一个kill
的信号试试:
1 | poll([{fd=4, events=POLLIN|POLLPRI|POLLHUP}, {fd=9, events=POLLIN|POLLPRI|POLLHUP}, {fd=11, events=POLLIN|POLLPRI|POLLHUP}], 3, 1000) = 2 ([{fd=9, revents=POLLHUP}, {fd=11, revents=POLLHUP}]) |
此时,supervisord
收到了SIGCHLD
信号,它知道cat
进程挂了。然后,我们发现,这里调用了clone
系统调用,创建了一个新的子进程,pid
是207
。我们可以来看看是不是:
1 | [root@e2a14c00e7f6 ~]# ps -ef |
确实是207
。
所以,supervisord
这就实现了自动重启子进程的功能。
那么,为什么supervisord
无法监控守护进程呢?我们来继续做实验。
这里有一个Swoole Server
的例子:
1 |
|
我们可以先来确认一下程序是否可以手动启动成功:
1 | [root@e2a14c00e7f6 server]# php start.php |
我们发现,启动成功了。然后,我们需要杀死这个server
进程:
1 | [root@e2a14c00e7f6 server]# kill 337 |
确认没有问题之后,我们通过supervisor
来启动Swoole Server
(具体的supervisor ini
配置大家可以自己配一下):
1 | [root@e2a14c00e7f6 ~]# supervisorctl restart server |
然后我们发现启动失败了,查看日志可以看到:
1 | PHP Fatal error: Uncaught Swoole\Exception: failed to listen server port[127.0.0.1:9580], Error: Address already in use[98] in /root/codeDir/phpCode/swoole/server/start.php:5 |
说是端口被占用了。我们来看一下端口:
1 | [root@e2a14c00e7f6 server]# netstat -antp | grep 9580 |
我们发现,程序确实被我们的服务器给占用了。那么为什么会报这个错误呢?说明在supervisor
的接管下,server
被多次启动,并且是直接在server
还没有退出的情况下启动。
首先,我们来看一下服务器进程状态:
1 | [root@e2a14c00e7f6 server]# ps -ef |
我们发现,因为server
是以守护进程的方式启动的,所以master
进程的ppid
是1
。(因为守护进程的实现原理是fork + fork + exit
,所以,master
进程自然就被pid
为1
的进程接管了)
正是因为server
的父进程不是supervisor
了,所以,supervisor
此时不能正确的监控server
的状态。(至于有没有其他的操作实现监控,这我没有过多的去研究它)
我们现在通过strace
来看看。首先,kill
掉这个server
:
1 | [root@e2a14c00e7f6 server]# kill 234 |
1 | [root@e2a14c00e7f6 ~]# strace -p 153 |
然后我们另开一个终端,此时我们再次通过supervisor
来启动server
:
1 | [root@e2a14c00e7f6 server]# supervisorctl restart server |
在strace
的终端,我们可以看到如下输出:
1 | clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f161ad54a10) = 324 |
可以看到,收到了子进程退出的信息。因为我们的server
进程因为守护进程化,最初的那个子进程是退出了的。所以,supervisor
误认为server
是不正常退出,它又对server
进行了重启。但是实际上,我们的server
已经监听了端口了,所以supervisor
再次启动server
,就会报错了。