使用 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 類。

相關文章
相關標籤/搜索