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

Kingmax 發表於2019-08-11

在上一篇文章《使用 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 類。

看看自己是不是一個靠譜的程式設計師,來做題試試。https://job.xyh.io


相關文章

Swoole

swoole 學習

swoole架構分析,為什麼支援高併發?背後知識io多路複用同步非同步,阻塞非阻塞Reactor事件模型CSP程式設計模型參考文章swoole架構分析nginx/swoole高併發原理初探swoole
框架|PHP|微服務|Swoole

🚀 Hyperf 釋出 Swoole Enterprise 及 檢視 及 Task 元件 | 企業級的 PHP 微服務協程框架

更新內容本次更新主要新增了 Swoole Enterprise 元件 和 檢視 元件 和 Task 元件,同時強化了 JSON RPC 的異常處理和完善了 JSON RPC 在 TCP 協議下的服務註
Swoole

使用 SWOOLE 實現程式的守護(一)

一、 程式守護使用場景。後端經常會有類似這樣的場景,某個指令碼,需要不斷的重複執行,這個時候,最好有一個守護程式,幫助我們不斷地自動地拉起這些指令碼程式,讓它自動地重複執行。在 Linux/Unix
PHP|Swoole

Swoole 在 PHP-fpm/apache 中使用 task 功能

新建RedisServer.php程式碼如下<?phpuse Swoole\Redis\Server;$server = new Server("127.0.0.1", 9501, SWOOLE
Swoole

laradock swoole 執行環境

修改程式碼目錄 APP_CODE_PATH_HOST=【你的專案地址】 變數國內使用者下載配置 CHANGE_SOURCE=true修改 WORKSPACE_INSTALL_SWOOLE=true &
Laravel|Swoole

Laradock~Laravel-swoole 搭建中遇到的問題

1.編輯docker目錄下workerspace/Dockerfile 新增EXPOSE埠號;2.編輯docker目錄下docker-compose.yml workspace -ports 新增"埠
Laravel|Swoole

在 wsl 下使用 swoole 膠水專案 laravels 艱辛的問題求解之路

前提概要有一個新專案由於未知原因專案載入速度很慢 不清楚是不是 laravel 框架的原因,畢竟效能一直是它的軟肋。不管是怎麼配置優化 甚至直接寫了一個最簡單的路由到頁面輸出 hello word,問
Swoole

swoole 協程原始碼解讀

我們按照執行流程去逐步分析swoole協程的實現, php程式是這樣的:<?phpgo(function (){ Co::sleep(1); echo "a";});echo "c"
Swoole

swoole 協程原始碼解讀 (協程的排程)

以下程式碼基於swoole4.4.5-alpha, php7.1.26那麼定時事件什麼時候會被執行呢? 這是通過內部的Reactor事件迴圈去實現的, 下面來看具體實現:建立協程時會判斷reactor
資料庫|MySQL|Swoole

SMProxy 分析 (基於 Swoole 開發的 MySQL 資料庫連線池)

前言: 在深入瞭解SMProxy之前,一直認為連線池是對mysql連線物件進行統一管理的處理,但是隨之而來的問題是現有的php框架都沒有自帶mysql連線池,如何以最小的代價替代框架的資料庫模組
WebSocket|Swoole

【Swoole】從原始碼中查 Websocket 連線問題

問題我們專案的Websocket Server使用的Swoole,最近在搭建 beta 環境的時候發現 Websocket 協議雖然升級成功了,但是會出現定時重連,心跳、資料也一直沒有傳送。專案的生產
Swoole

Swoole 回撥函式的註冊與呼叫

swoole的回撥函式大致分為兩種。一種是事件回撥,是swoole啟動執行時觸發的回撥。另一種是埠回撥,這類回撥的特點是都有fd引數傳入。//註冊回撥的函式。static PHP_METHOD(swo