Рубрики
Uncategorized

Использование swool для реализации демонов процесса (3)

Автор оригинала: David Wong.

В предыдущей статье “использование swool для реализации демонов процессов (2)” реализован класс демонов, который может одновременно запускать несколько сценариев с помощью конфигурации чтения. В этой статье предпринята попытка продолжить расширение класса демона, чтобы он мог реализовать настроенную перегрузку без перезапуска процесса. Наиболее распространенным способом горячей перегрузки является отправка системных сигналов в процесс. Когда процесс прослушивает соответствующие сигналы, достаточно перезагрузить память, настроенную в пространстве процесса. Высокопроизводительные резидентные серверы процессов, такие как nginx и caddy, также могут быть перегружены таким образом, чтобы избежать недоступности сервера, вызванной перезапуском процесса.

В bash Linux вы можете просматривать все поддерживаемые сигналы процесса с помощью команды 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.

PHP официально предоставляет две функции для обработки номера процесса, а именно:

  1. Pcntl_signal (SIGINT, “обработчик сигнала”); используется для регистрации функции обработки после получения сигнала.
  2. Pcntl? Сигнал? Функция Dispatch() используется для вызова процессора, зарегистрированного pcntl? Сигнал() для каждого сигнала ожидания.

Тогда пример кода зарегистрированного сигнального процессора может быть похож на следующий:

pcntl_signal(SIGHUP, function () {
    Printf ("received overload configuration signal \ n");
    $this->loadWorkers();
    Printf ("reload configuration complete \ n");
});

Процессор сигналов планирования может выполнять каждый раз, когда процесс проверки повторяется:

while (1) {
    pcntl_signal_dispatch();
    if ($ret = Process::wait(false)) {
        // todo something
    }
}

Таким образом, класс демона может быть расширен следующим образом:

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 ("received overload configuration signal \ n");
            $this->loadWorkers();
            Printf ("reload configuration complete \ n");
        });

        $this->waitAndRestart();
    }

    /**
     *Retract process and restart
     */
    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] remove guard% 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] pull% s again \ n", date ("y-m-d H: I: s"), $this - > workers [$index] - > getcommand() - > getid());
                    }
                }

            }
        }
    }


    /**
     *Load workers
     */
    private function loadWorkers()
    {
        $this->parseConfig();
        foreach ($this->commands as $command) {
            if ($command->isEnabled()) {
                Printf ("[% s] enable% s \ n", date ("y-m-d H: I: s"), $command - > getid());
                $this->startWorker($command);
            } else {
                Printf ("[% s] disable% s \ n", date ("y-m-d H: I: s"), $command - > getid());
                $this->stopWorker($command);
            }
        }
    }

    /**
     *Start 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;
        }
    }

    /**
     *Stop 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;
    }

    /**
     *Resolve profile
     */
    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;
            }
        }
    }

    /**
     *Create a subprocess and return the subprocess 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();
    }

}

Примечание. для упрощения кода в приведенный выше код добавляется рабочий класс следующим образом:

class Worker
{
    /**
     * @var Command
     */
    private $command;

    /**
     * @var int
     */
    private $pid;

    /**
     * @var bool
     */
    private $stopping;

    //... the get set method is omitted below
}

Наконец, использование этого класса демонов по-прежнему выглядит следующим образом:

$pid = posix_getpid();
Printf ("main process number: {$PID} \ n");

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

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

Затем, если мы знаем, что номер процесса запущенной демонической программы равен 522, настроенная горячая перегрузка может быть реализована следующей командой:

kill -USR1 522

На данный момент функция этого класса демонов завершена, но все еще есть некоторые улучшения, например, есть ли способ автоматически применить последнюю конфигурацию самой программой без того, чтобы пользователь вручную посылал сигнал процессу для перезагрузки конфигурации?

В следующей статье мы будем использовать демонов процесса реализации swool (4), чтобы продолжить расширение класса демонов.