背景
之前听过这个工具,但是没用过它,只知道它是一个进程管理的工具。然后最近我在公司要用到这个东西来部署
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,就会报错了。