在上一篇文章《使用 swoole 实现进程的守护(二)》中,实现了一个能经过读取配置同时守护多个脚本的 Daemon 类。
本文尝试继续扩展这个 Daemon 类,让它可以在不重启进程的状况下实现配置的重载。
最多见的一种热重载的方式,就是向进程发送系统信号,当进程监听到相应信号时,即执行从新加载配置到进程空间的内存便可。
像 Nginx 和 Caddy 这种高性能的常驻进程服务器,为了不重启进程致使的服务器不可用,也是经过这种方式来实现热重载的。bash
在 Linux 的 bash 能够经过 kill -l 命令来查看全部支持的进程信号服务器
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
咱们能够经过选择监听用户自定义信号 SIGUSR1 来实现。swoole
PHP 官方提供了两个函数来处理进程号,分别是:函数
那么,注册信号处理器的示例代码能够相似以下:性能
pcntl_signal(SIGHUP, function () { printf("收到重载配置信号\n"); $this->loadWorkers(); printf("重载配置完成\n"); });
而调度信号处理器能够在每次检查进程回收的时候执行:this
while (1) { pcntl_signal_dispatch(); if ($ret = Process::wait(false)) { // todo something } }
因而,Daemon 类能够扩展以下:spa
namespace App; use Swoole\Process; class Daemon { /** * @var string */ private $configPath; /** * @var Command[] */ private $commands; /** * @var Worker[] */ private $workers = []; public function __construct(string $configPath) { $this->configPath = $configPath; } public function run() { $this->loadWorkers(); pcntl_signal(SIGHUP, function () { printf("收到重载配置信号\n"); $this->loadWorkers(); printf("重载配置完成\n"); }); $this->waitAndRestart(); } /** * 收回进程并重启 */ private function waitAndRestart() { while (1) { pcntl_signal_dispatch(); if ($ret = Process::wait(false)) { $retPid = intval($ret["pid"] ?? 0); $index = $this->getIndexOfWorkerByPid($retPid); if (false !== $index) { if ($this->workers[$index]->isStopping()) { printf("[%s] 移除守护 %s\n", date("Y-m-d H:i:s"), $this->workers[$index]->getCommand()->getId()); unset($this->workers[$index]); } else { $command = $this->workers[$index]->getCommand()->getCommand(); $newPid = $this->createWorker($command); $this->workers[$index]->setPid($newPid); printf("[%s] 从新拉起 %s\n", date("Y-m-d H:i:s"), $this->workers[$index]->getCommand()->getId()); } } } } } /** * 加载 workers */ private function loadWorkers() { $this->parseConfig(); foreach ($this->commands as $command) { if ($command->isEnabled()) { printf("[%s] 启用 %s\n", date("Y-m-d H:i:s"), $command->getId()); $this->startWorker($command); } else { printf("[%s] 停用 %s\n", date("Y-m-d H:i:s"), $command->getId()); $this->stopWorker($command); } } } /** * 启动 worker * @param Command $command */ private function startWorker(Command $command) { $index = $this->getIndexOfWorker($command->getId()); if (false === $index) { $pid = $this->createWorker($command->getCommand()); $worker = new Worker(); $worker->setPid($pid); $worker->setCommand($command); $this->workers[] = $worker; } } /** * 中止 worker * @param Command $command */ private function stopWorker(Command $command) { $index = $this->getIndexOfWorker($command->getId()); if (false !== $index) { $this->workers[$index]->setStopping(true); } } /** * * @param $commandId * @return bool|int|string */ private function getIndexOfWorker(string $commandId) { foreach ($this->workers as $index => $worker) { if ($commandId == $worker->getCommand()->getId()) { return $index; } } return false; } /** * @param $pid * @return bool|int|string */ private function getIndexOfWorkerByPid($pid) { foreach ($this->workers as $index => $worker) { if ($pid == $worker->getPid()) { return $index; } } return false; } /** * 解析配置文件 */ private function parseConfig() { if (is_readable($this->configPath)) { $iniConfig = parse_ini_file($this->configPath, true); $this->commands = []; foreach ($iniConfig as $id => $item) { $commandLine = strval($item["command"] ?? ""); $enabled = boolval($item["enabled"] ?? false); $command = new Command(); $command->setId($id); $command->setCommand($commandLine); $command->setEnabled($enabled); $this->commands[] = $command; } } } /** * 建立子进程,并返回子进程 id * @param $command * @return int */ private function createWorker(string $command): int { $process = new Process(function (Process $worker) use ($command) { $worker->exec('/bin/sh', ['-c', $command]); }); return $process->start(); } }
注意:为了代码简洁,以上代码新增了一个 Worker 类以下:code
class Worker { /** * @var Command */ private $command; /** * @var int */ private $pid; /** * @var bool */ private $stopping; // ... 如下省略了 Get Set 方法 }
最后,这个 Daemon 类的使用方法,仍然是:协程
$pid = posix_getpid(); printf("主进程号: {$pid}\n"); $configPath = dirname(__DIR__) . "/config/daemon.ini"; $daemonMany = new Daemon($configPath); $daemonMany->run();
那么,假如咱们知道 Daemon 程序正在运行的进程号为 522,则可经过如下命令来实现配置的热重载:进程
kill -USR1 522
到目前为止,这个 Daemon 类能够说是功能完备了,可是仍有能够改进的地方,
好比,有没有办法不须要用户手动去给进程发送信号来重载配置,由程序本身去自动应用最新的配置呢?
下一篇文章 使用 swoole 实现进程的守护(四)将 swoole 的协程尝试继续扩展这个 Daemon 类。