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

发表评论

电子邮件地址不会被公开。