使用 SWOOLE 實現進程的守護(二)

在上一篇文章《使用 swoole 實現進程的守護(一)》中,初步實現了一個能自動重啓子進程的 Daemon 類。
但是這個 Daemon 類有一個很明顯的缺點,就是隻支持單個子進程的守護。

一、支持守護多個腳本程序

實際情況下,通常都會有多個子進程需要守護,要擴展這個 Daemon 也很簡單,只需要將構造函數的參數從 string 更改爲 array 即可。
支持守護多個腳本的 Daemon 類,改寫如下:

use Swoole\Process;

class Daemon
{
    /**
     * @var string[]
     */
    private $commands;

    /**
     * @var array
     */
    private $workers = [];


    public function __construct(array $commands)
    {
        $this->commands = $commands;
    }

    public function run()
    {
        foreach ($this->commands as $index => $command) {
            $pid = $this->createWorker($command);
            $this->workers[$pid] = $command;
        }
        $this->waitAndRestart();
    }

    private function waitAndRestart()
    {
        while (1) {
            if ($ret = Process::wait(false)) {
                $retPid = intval($ret["pid"] ?? 0);

                if (isset($this->workers[$retPid])) {

                    $command = $this->workers[$retPid];
                    $newPid = $this->createWorker($command);

                    $this->workers[$newPid] = $command;
                    unset($this->workers[$retPid]);
                }
            }
        }
    }

    /**
     * 創建子進程,並返回子進程 id
     * @param $command
     * @return int
     */
    private function createWorker($command): int
    {
        $process = new Process(function (Process $worker) use ($command) {
            $worker->exec('/bin/sh', ['-c', $command]);
        });
        return $process->start();
    }

}

代碼解析:
運行 run() 方法,將會創建各個子進程,然後使用 waitAndRestart() 等待,一旦有運行結束的子進程則重新拉起新的子進程。

這個新的 Daemon 類的使用方式,可以類似這樣:

$php = "/usr/bin/env php";
$script1 = dirname(__DIR__) . "/task1.php";
$script2 = dirname(__DIR__) . "/task2.php";

$commands = [
    "{$php} {$script1}",
    "{$php} {$script2}",
];

$daemon = new Daemon($commands);
$daemon->run();

但是這樣的使用方式,仍然是不夠方便的,畢竟要新增或減少要守護的程序,還得改以上的代碼,參考 supervisor,可以使用配置文件的方式來支持動態的修改要守護的程序。

二、支持使用配置文件

PHP 有個內置的函數 parse_ini_file() 可以解析 .ini 後綴的配置文件,爲了方便,可以使用 .ini 文件作爲配置。

首先定義一個程序的配置格式如下:

[task-1]
command = "/usr/bin/env php /var/www/html/task/task1.php"

表示守護一個 id 爲 task-1 的程序,其運行命令爲 /usr/bin/env php /var/www/html/task/task1.php

定義一個 Command 類來表示這個配置:

class Command
{
    /**
     * 工作進程 id
     * @var string
     */
    private $id;

    /**
     * 真正執行的 command 命令
     * @var string
     */
    private $command;

    // ... 以下省略了相關的 get set 方法 ...

}

同樣的,只需要將 Daemon 類的構造函數參數改爲配置文件路徑,於是,一個支持配置文件的 Daemon 類,則可改寫如下:

use Swoole\Process;

class Daemon
{
    /**
     * @var string
     */
    private $configPath;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var array
     */
    private $workers = [];

    public function __construct(string $configPath)
    {
        $this->configPath = $configPath;
    }


    public function  run()
    {
        $this->parseConfig();
        foreach ($this->commands as $command) {
            $pid = $this->createWorker($command->getCommand());
            $this->workers[$pid] = $command->getCommand();

        }
        $this->waitAndRestart();
    }

    /**
     * 收回進程並重啓
     */
    private function waitAndRestart()
    {
        while (1) {
            if ($ret = Process::wait(false)) {
                $retPid = intval($ret["pid"] ?? 0);

                if (isset($this->workers[$retPid])) {

                    $commandLine = $this->workers[$retPid];
                    $newPid = $this->createWorker($commandLine);

                    $this->workers[$newPid] = $commandLine;
                    unset($this->workers[$retPid]);
                }
            }
        }
    }


    /**
     * 解析配置文件
     */
    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"] ?? "");

                $command = new Command();
                $command->setId($id);
                $command->setCommand($commandLine);
                $this->commands[] = $command;
            }
        }
    }

    /**
     * 創建子進程,並返回子進程 id
     * @param $command
     * @return int
     */
    private function createWorker($command): int
    {
        $process = new Process(function (Process $worker) use ($command) {
            $worker->exec('/bin/sh', ['-c', $command]);
        });
        return $process->start();
    }

}

代碼解析:
主要的改動在於新增了 parseConfig() 方法,以完成讀取配置文件內容的功能。

編寫配置文件 daemon.ini 內容如下:

[task-1]
command = "/usr/bin/env php /var/www/html/task/task1.php"

[task-2]
command = "/usr/bin/env php /var/www/html/task/task2.php"

最終,這個 Daemon 類的使用方式,可以類似這樣:

$configPath = dirname(__DIR__) . "/config/daemon.ini";

$daemonMany = new Daemon($configPath);
$daemonMany->run();

三、結尾

到目前爲止,可以說,這個 Daemon 類已經算是比較靈活了,但仍有不足的地方,例如,由於這是個常駐進程,一旦修改了配置文件,想要配置文件生效,勢必要重啓父進程,有沒有辦法在不重啓父進程的情況下,讓配置生效?

下一篇文章 使用 swoole 實現進程的守護(三)將結合進程的信號與 swoole 的協程嘗試繼續擴展這個 Daemon 類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章