前言
只针对linux下的重启解析,windows下没有重启和平滑重启的策略。代码区域只展示方法名称和部分的代码片段,完整代码需要自行参阅workerman
项目。下述的名词“主进程”和“父进程”是同一个意思。
梳理流程
命令入口:php workerman.php reload -g
进入\Workerman\Worker::parseCommand()
:
workerman主进程
收到进程信号,进入\Workerman\Worker::signalHandler()
:
workerman主进程
开始向子进程
逐个发送退出信号,进入\Workerman\Worker::reload()
:
workerman子进程
开始处理退出业务,进入\Workerman\Worker::stopAll()
:
这时候子进程已经完全退出,父进程收到子进程退出的信号,进入\Workerman\Worker::monitorWorkersForLinux()
:
“让对应进程处理完毕当前请求后才退出”是如何做到的
先结合代码看\Workerman\Events\Select::loop
:
这里有个pcntl_signal_dispatch
函数,这是用于分发进程信号的,如果收到信号会马上执行\Workerman\Worker::signalHandler()
。值得注意的是pcntl_signal_dispatch
和信号回调是并行运行的,所以执行到stopAll()
时进程就退出了,下面的事件处理代码不再执行。
通俗来讲,主进程向子进程发送信号时,子进程的轮询器并没有立即收到信号,直到发送信号后的下一次while
循环体执行时才收到信号(先处理完当前的任务),收到信号后进程开始处理退出逻辑,这里就是所谓让对应进程处理完毕当前请求后才退出
。
平滑重启过程应该注意些什么
- 如果没有额外扩展比如
libevent
、\Swoole\Event
加持时,在定时器或者触发其他事件时,不应有长睡眠或者while(true)
的业务,否则队列中排在该子进程后面的进程将无法得到重启。
- 不要在子进程中保留客户端的长期数据,因为就算没有人为重启,进程运行过程中有意外退出时框架也会自动重启该进程,重启后就是一个新的进程,包括内存信息也是新的。
- 因为重启过程是针对子进程的,主进程不会进行重启,所以如果要更新主进程代码信息时需要把主进程也手动重启一遍。
一些疑问
case 'reload'
时执行了exit
,进程就退出了,为什么能继续重启?
因为执行php workerman.php reload -g
的时候,系统新开了一个进程,它负责向正在运行的workerman进程发送信号,这属于两个进程组,退出任何一个都相互不影响。
一定要平滑重启吗?
并不是,平滑重启和普通重启的区别在于,普通重启会产生一个延时定时器,用于在进程退出超时(默认是2s)后将进程强制kill掉并重启,所以需要保证你的子进程业务中没有长时间堵塞IO的代码。对于上述“让对应进程处理完毕当前请求后才退出”的策略是贯穿于普通重启和平滑重启的,所以两种重启策略不需要关心是否影响当前请求。
哪些是属于主进程的代码?
举个例子:
通过上述代码不难看出,其实几乎所有的回调函数都属于是子进程,而写在回调外面的代码都属于是主进程代码,主进程负责启动和监视子进程。