守护的梦想
有时候我们在运行服务端系统的时候会遇到这样的问题,希望一个进程能常驻,而不是随着会话的结束而结束,常见如Mysql、Apache等web服务都有类似的需求。
这种不会随着会话退出而自己结束的进程,可以叫做守护进程(Daemon),而像Mysql、Apache这类程序,已经很贴心的提供了守护进程的配置,甚至是默认就是已守护进程的形式工作的。
其实守护进程还有更严谨的定义,这里就不展开了
但有时候我们如果需要运行一个自己的程序,或者某个不支持守护进程的程序时,就需要做些额外的动作让这个进程不会随着会话退出而退出
所以经常看到类似mysqld、httpd啥的,这里的d就是Daemon的意思
贴心点的如swoole server,在配置中补上一个daemon配置即可,麻烦点如beego的server,可以用类似这样的命令:
nohup path/XXX &
严格来说,这样启动的并不是守护进程,不过从结果来说,也可以说它已经是“守护进程”了
总所周知,linux系统中有一个很重要的信号机制,进程可以通过一系列信号进行通讯,例如说,经常为了关闭一个进程使用的
kill -TERM <
而用户结束当前会话的时候,会向当前会话的子进程发送一个HUP信号,一般情况下,当前会话的子进程收到HUP信号以后就会退出自己。
也就是说对于交互进程,kill -hup <#pid#>也可以起到退出进程的作用。
看到这里,相信nohup的意思就很明确了,“No HUP”,使用了这个命令以后,启动的进程将忽略HUP信号;
nohup之后还可以配合其他命令重定向输出等,这里就不展开啦
也就是说,因为忽略了HUP的信号,当前会话结束以后,该进程不会自动退出,而是被系统主进程收养(pid = 1),也就产生了形如守护进程的效果。
而&表示将当前进程放入后台执行。
利用nohup命令,我们可以很方便的根据自己的需要启动守护进程,以达到我们的目的,例如对外提供web服务。
简单粗暴好用,再好不过了
如果说nohup命令是用户在使用时根据自己需要实现的守护进程,有些时候作为工具的制造者的攻城狮也可以让程序运行时自动蜕变成守护进程的方法
适度的封装是为了降低使用者的门槛
比较传统的守护进程程序实现思路如下:
- 创建子进程,父进程退出
- 在子进程中创建新会话
beego的graceful实现就是fork出子进程以后,子进程反手就把把父进程关闭了 ———— 总觉得是一出麦克白…
由于父进程是由会话终端启动的,此时父进程就隶属于会话进程,如果此时会话退出,父进程也会由于收到hup信号而退出
其实父进程只是个壳,子进程才是真正处理业务的进程,有点像鲧和禹的传说
但如果父进程主动退出以后,子进程就变成了孤儿进程,而善良的Linux系统进程(pid = 1)就会收养孤儿进程,这个时候子进程就已经从组织上脱离会话进程了
独立成人总是长大的一部分,但童年的经历也是人生的一部分
子进程仍然会继承父进程与会话有关的会话期的信息,此时需要让子进程自己创立自己的会话,并担任会话组的组长
就像成人仪式一样,宣布正式脱离原会话的控制、原进程组的控制、原控制终端的控制
具体实现上,不同的语言有不同的写法,所以这里只简单介绍一下原理咯~
一般来说就是fork和 … 你猜?
守护 ~ 守护进程 ~ 进程
守护进程虽然不会因为会话的退出而退出,却有可能因为各式各样的原因出现异常而崩溃。
一些web服务往往要求有比较高的可用性,服务进程崩溃以后,用户往往看到的就是404了
但大多时候都是稳定的服务,偶发性的崩溃却很难及时的通知到管理员
毕竟很多程序不是自己写的,要在程序内部做点监听啥的还真不容易(也没必要)
而且很多时候,只要重启一下服务即可,如果有一个方法可以监视着服务进程,如果发生了异常的退出则自动重启服务进程,就好了
例如丶运维攻城宅可以7*24小时每隔2秒刷新一下网页,看看有没有404 …
显然这种工具是有的,Supervisor就是其中一个。
Supervisor
Supervisor是一个用Python写的进程管控工具,简单地说,它主要解决了两个问题:
- 管:进程的启动和守护进程化。
- 控:监控进程是否异常退出,根据需要重启进程。
当然,Supervisor还可以做得更多…
关于Supervisor的安装说明网上很多,这里就不一一细说了,我们还是来聊聊Supervisor可以怎么管及怎么控你需要的进程。
进程配置文件简析
一个简单的Supervisor进程配置文件可能是这样的:
[program:nginx]
command=/usr/sbin/nginx ;the program (relative uses PATH, can take args)
没错,就这么简单,你只需要把启动进程的命令写进去就可以了,剩下的事情,Supervisor会自动帮你搞定。
Sometimes, simple is the best
这个配置文件中有两个关键属性,第一个是第一行中的“nginx”,这个参数表明了对于Supervisor来说,这个进程应该怎么称呼
毕竟每次运行的时候,进程号都是新分配的,用进程号显然极不方便
第二个属性显然是command,这里就跟你在shell中调用一样,把你想启动的程序的命令写进去吧,可以带参数,也可以不带。
同样,更多的配置略
Supervisord和Supervisorctl
安装完Supervisor以后,系统中一般会找到两个工具(程序),一个是supervisord,另一个是supervisorctl,前者是主要的工作程序,后者则是一个管理工具,两者的关系类似apache和apachectl
事实上,ctl的基本工作就是把人比较好理解的命令转译给正在工作的d,就像高级语言,最终会被转成cpu指令执行一样
首先使用superviaord启动supervisor的主进程,然后通过supervisorctl向主进程发送一系列命令,让它听你的命令工作。
木有错,这里的d就是前文说的Daemon的意思
首先,supervisord启动后,其自身就会蜕变成一个守护进程,然后,它会根据用户的配置文件,根据用户启动配置启动用户需要的进程,而用户配置的进程就变成了supervisord的子进程
这里可不需要nohup ~
如果子进程发生了什么意外导致退出,supervisord进程会收到子进程挂掉的消息,然后重新启动子进程
默认情况下,supervisor的意外表示进程的 exit_code != 0 || exit_code != 2
这样就是守护-守护进程-的进程,由supervisor替管理员管理服务进程的生命周期
常用的命令如supervisorctl start|reload|stop|update这里就不细说咯
意外,总是如期而至
事实上,用supervisor启动守护进程,某种程度上有点像nohup的方法,把自身非守护进程的进程当作守护进程再运行,所以如果某个程序本身就是默认以守护进程的方式运行的,就会出问题
还记得前文中介绍的自身守护进程化的方式么?
我们假设用supervisor启动了默认的nginx服务,刚启动的一瞬间,进程的结构可以简单看作是这样的:
systemd pid = 1
\- supervisord pid = 1234
\= nginx pid = 5678
但nginx默认是自身守护进程化的,也就是说,这个时候pid=5678的进程是父进程,此时他会根据自己的逻辑fork出子进程(假设pid = 8765)用于处理业务:
systemd pid = 1
\- supervisord pid = 1234
\= nginx pid = 5678
\- nginx pid = 8765
然后,父进程(pid = 5678)退出,子进程(pid = 8765)被主进程(pid = 1)收养:
systemd pid = 1
|
\- supervisord pid = 1234
|
\- nginx pid = 8765
这时,问题来了,supervisor发现自己的小弟nginx(pid=5678)退出了,真正的业务进程()pid = 8765)并不是组织(super联萌)的人,那么也就是说,nginx本意是为了脱离会话的控制而华为守护进程的行为,也导致了它无法被supervisor守护
毕竟,人是社会性动物,人设计的程序、系统、制度,或多或少都会体现这一点
那么解决的方法是什么呢?也很简单,告诉nginx不要自己蜕变成守护进程即可:
daemon off;
此时,nginx就不会主动的守护自身,而是把这个权利让渡给了supervisor,由supervisor守护自己
是不是有点公民权利让渡的味道(^^__^^)
除了supervisor,常用的apahce也有类似的问题,解决方式如下:
command = PathToApache/apache2ctl -DFOREGROUND;
后记
关于守护进程,从早年在不知情的情况下用apahce、mysql,到在学习swoole的时候第一次知道守护进程的概念,从自己写的process守护,到使用supervisor,一路走来,今天写这篇稿的时候忽然有点唏嘘。
物似人非,事事休
也许成长就是学会接受变化。