В предыдущей статье “использование 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 официально предоставляет две функции для обработки номера процесса, а именно:
- Pcntl_signal (SIGINT, “обработчик сигнала”); используется для регистрации функции обработки после получения сигнала.
- 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), чтобы продолжить расширение класса демонов.