Демон — это программа в системах класса UNIX, запускаемая самой системой и работающая в фоновом режиме без прямого взаимодействия с пользователем. Если это понятие применить к нашей задаче, т.е. к языку программирования PHP, то можно сделать следующие выводы: Демон — это обычный PHP скрипт выполняемый интерпретатором. PHP демон работает на сервере в фоновом режиме и не взаимодействует на прямую с пользователем.
Применимо к PHP, это скрипт, который может работать самостоятельно, без остановок и без участия пользователя. Как получить такой скрипт? На самом деле, очень просто, нужно лишь нарушить одно из первых правил программирования, которому учат в школе, и создать бесконечный цикл:
// Чтобы программа работала постоянно, она просто должна постоянно работать ;)while(1) { // Тут будет располагаться код Демона // ... // Время сна Демона между итерациями (зависит от потребностей системы) sleep(1); }
Простой до невозможности код вызывает, всё же, несколько вопросов. Как его запустить? Как отслеживать его выполнение? Как его остановить?
Как запустить php-демона
А как вообще запускают php-скрипты? Если это веб-приложение, то при помощи браузера и веб-сервера. Но этот вариант не подходит, ведь мы имеем дело с бесконечным скриптом, а время выполнения скриптов ограничены директивой max_execution_time в php.ini. Следовательно, бесконечный скрипт необходимо запускать через консоль, ведь тогда максимальное время его выполнения не учитывается. Примерно так выглядит команда запуска демона:
php -f /path/to/your/daemon.php &
Для ручного запуска её нужно ввести в ssh терминале (putty, WinSCP и т.д.), а для запуска системой при загрузке — в соответствующий файл автозагрузки (положение и название файла зависит от операционной системы). Обратите внимание, что консольный скрипт демона запускается в фоновом режиме, не вовлекая пользователя в ожидание его завершения (ведь скрипт бесконечен). Именно в наличии возможности запустить процесс в фоновом режиме и лежит причина того, что описываемый мной способ не подходит для Windows-серверов. После запуска в консоли должен отобразиться идентификатор процесса нашего демона, так называемый PID.
Отслеживание и остановка демонов
Проверить, запущен ли процесс демона можно просто открыв список процессов в системе:
ps -aux
Найти демона в списке процессов несложно, как по команде запуска, так и по PID:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ... root 22193 0.1 0.2 393180 72132 ? S Apr24 5:05 php -f /path/to/your/daemon.php &
Остановить процесс демона можно так же, как и любой другой процесс:
kill xxxx
В приведённом примере xxxx — это и есть PID, идентификатор процесса.
Для реализации демона на php нам понадобится расширение PCNTL. Данное расширение дает возможность управлять запущенными процессами. Убедились, что данное расширение присутствует на Вашем сервере! Тогда рассмотрим каркас простейшего демона.
$stop = false;/** * pcntl_fork() - данная функция разветвляет текущий процесс */$pid = pcntl_fork();if ($pid == -1) { /** * Не получилось сделать форк процесса, о чем сообщим в консоль */ die('Error fork process' . PHP_EOL);} elseif ($pid) { /** * В эту ветку зайдет только родительский процесс, который мы убиваем и сообщаем об этом в консоль */ die('Die parent process' . PHP_EOL);} else { /** * Бесконечный цикл */ while(!$stop) { /* * Тело демона */ }}/** * Установим дочерний процесс основным, это необходимо для создания процессов */posix_setsid();
Весь код сводится к тому, чтобы после запуска распараллелить скрипт и завершить родительский процесс, тем самым отвязаться от консоли и уйти в фон. Для того, чтобы наш демон не закончился раньше времени, необходимо его пустить в бесконечный цикл, но стоит оставить возможность нормального завершения работы. Рассмотрим более подробно.
Бесконечный цикл реализован при помощи конструкции while и переменной $stop. Данная переменная нам необходима для корректного завершения процесса, например, так:
... while(!$stop) { /* * Тело демона */ for ($i = 0; $i < 10; $i++) { file_put_contents('/var/data/tmp/' . $i . '.txt', time()); sleep(2); } //Завершим корректно выполнение демона $stop = true; }...
В данном примере демон за 20 секунд создает 10 файлов и присваивает переменной $stop значение true, на следующей итерации бесконечный цикл while прекратится и программа завершит работу. В данном примере мы рассмотрели однопроцессорный демон на языке программирования PHP
Запуск демона
Для запуска подключаемся по SSH и запускаем скрипт:
/usr/bin/php /var/data/deamon.php & >/dev/null
...//Без этой директивы PHP не будет перехватывать сигналыdeclare(ticks=1);//Обработчикfunction sig_handler($signo) { switch ($signo) { case SIGTERM: // Обработка задач остановки exit; break; case SIGINT: // обработка CTRL+C break; case SIGHUP: // обработка задач перезапуска break; case SIGUSR1: echo "Получен сигнал SIGUSR1...n"; break; default: // Обработка других сигналов }}// Установка обработчиков сигналов. Подробнее читаем тут https://www.php.net/manual/ru/function.pcntl-signal.phppcntl_signal(SIGTERM, "sig_handler");pcntl_signal(SIGHUP, "sig_handler");pcntl_signal(SIGINT, "sig_handler");pcntl_signal(SIGUSR1, "sig_handler");
Обычно для этих целей используются т.н. .pid-файлы — файл, в котором записан pid данного конкретного демона, если он запущен.
function isDaemonActive($pid_file) { if( is_file($pid_file) ) { $pid = file_get_contents($pid_file); //проверяем на наличие процесса if(posix_kill($pid,0)) { //демон уже запущен return true; } else { //pid-файл есть, но процесса нет if(!unlink($pid_file)) { //не могу уничтожить pid-файл. ошибка exit(-1); } } } return false;}if (isDaemonActive('/tmp/my_pid_file.pid')) { echo 'Daemon already active'; exit;}
А после демонизации — нужно записать в pid-файл текущий PID демона.
file_put_contents('/tmp/my_pid_file.pid', getmypid());
Контролировать процессы можно через Supervisor
$ apt install supervisor
Настройка Supervisor состоит из двух частей: конфигурационного файла supervisord и самой программы, предназначенной для постоянного выполнения в фоне.
Конфиг сервиса
Конфигурационные файлы сервисов Supervisor находятся в /etc/supervisor/conf.d. Логично будет назвать сервис как поддомен для сайта, к которому этот сервис относится, то есть для example.com это будет service.example.com.conf. Если же сервис один, то можно не усложнять и назвать его как самого пользователя.
Если задачи выполняются от пользователя example.com из корня сайта который живет в домашнем каталоге этого пользователя, то конфигурационный файл получается следующий:
[program:service.example.com]user = example.comcommand = php bin/run-service.phpdirectory = /home/example.com/wwwnumprocs = 1autorestart = trueautostart = truestdout_logfile = /home/example.com/www/logs/supervisor_service.logstderr_logfile = /home/example.com/www/logs/supervisor_service_errors.logstopwaitsecs = 60
С этим конфигом для запуска сервиса Supervisor сначала перейдет в /home/example.com/www, затем запустит команду php bin/run-service.php и будет следить за её работой, перезапуская при падении. Описание всех директив можно найти в официальной документации.
Чтобы Supervisor увидел новый сервис нужно перезапустить его самого.
$ sudo service supervisor restart
Для проверки перезапустим сервис и остановим его, подождав немного:
$ supervisorctl restart service.example.com
$ supervisorctl stop service.example.com
Что ещё нужно иметь ввиду?
После обновления исходных кодов нужно обязательно перезапускать все подобные сервисы чтобы они использовали новую кодовую базу. Это можно делать прямо из хука post-receive или из Makefile, если такой используется для сборки ресурсов после выгрузки новый версии.
$ sudo /usr/bin/supervisorctl restart service.example.com
К сожалению, работа с supervisord требует повышенных привелегий, потому стоит дать возможность всем заслуживающим того пользователям возможность перезапускать то, что им потребно, через sudo без запроса пароля.
$ %users ALL = NOPASSWD: /usr/bin/supervisorctl restart *
Как менялся логотип Apple на протяжении многих лет. Логотип Apple — это не просто символ,…
Security Boot Fail при загрузке Acer — решение проблемы При загрузке ноутбука Acer с флешки,…
Ноутбук не включается — варианты решения Если при попытке включить ноутбук вы обнаруживаете, что он…
The AC power adapter wattage and type cannot be determined — причины и решение При…
Свистит или звенит блок питания компьютера — причины и решения Некоторые владельцы ПК могут обратить…
Мигает Caps Lock на ноутбуке HP — почему и что делать? При включении ноутбука HP…