使用 swoole 实现进程的守护(三)

在上一篇文章《使用 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 官方提供了两个函数来处理进程号,分别是:函数

  1. pcntl_signal(SIGINT, 'signalHandler'); 用于注册收到信号后的处理函数。
  2. pcntl_signal_dispatch() 用于调用每一个等待信号经过 pcntl_signal() 注册的处理器。

那么,注册信号处理器的示例代码能够相似以下:性能

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 类。

相关文章
相关标签/搜索