在上一篇文章《使用 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 類。
本作品採用《CC 協議》,轉載必須註明作者和本文連結