Вы имеете ввиду книгу авторов (Марк Митчел, Джеффри Оулдем, Алекс Самьюэл) ну так это пародия на язык. Те темы, которые авторы понимают описаны отлично, но где они сомневаются ограничено общими словами.
а что читать новичкам?
Закладки на сайте Проследить за страницей |
Created 1996-2023 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |
Источник: www.opennet.ru
Создание демона на PHP или скрипты, которые работают сами по себе
При разработке веб-приложений может возникнуть потребность в том, чтобы сервер что-то делал самостоятельно, без участия пользователя: занимался постобработкой данных, делал рассылки, отслеживал что-то, обрабатывал. Когда речь заходит о самостоятельной работе сервера, первое, что приходит в голову вебмастеру, привыкшему работать c Unix-подобными серверами, это программа Cron, которая может выполнять команды по расписанию. Но как быть, если не существует никакого расписания, и какой-то скрипт должен быть запущен постоянно и работать сам по себе, запускаясь заново сразу после своего выполнения? Вдохновлённый разработкой сервиса CheckTrust, вообразив себя Некромантом, я решил поделиться своим опытом в создании таких скриптов-демонов на PHP.
КАК ИСПРАВИТЬ ОШИБКУ ДЕМОНА?ОТВЕТ ЗДЕСЬ! |Game Guardian|
Сразу оговорюсь, что всё, о чём будет идти речь в этой статье, применимо лишь для Unix-подобных операционных систем, и не будет работать под Windows. Речь идёт именно об альтернативе крону. Несчастным любителям Windows придётся колдовать с «Планировщиком заданий» — Чёрная Магия останется недоступна для них 🙂 Кроме того, потребуется ssh доступ, а в идеале — доступ php скриптов к командной строке.
Что есть программа-демон и как написать демона на PHP
Де́мон (daemon, dæmon, др.-греч. δαίμων божество) — компьютерная программа в системах класса UNIX, запускаемая самой системой и работающая в фоновом режиме без прямого взаимодействия с пользователем. Демоны обычно запускаются во время загрузки системы.
Применимо к PHP, это скрипт, который может работать самостоятельно, без остановок и без участия пользователя. Как получить такой скрипт? На самом деле, очень просто, нужно лишь нарушить одно из первых правил программирования, которому учат в школе, и создать бесконечный цикл:
// Чтобы программа работала постоянно, она просто должна постоянно работать 😉 while(1) < // Тут будет располагаться код Демона // . // Время сна Демона между итерациями (зависит от потребностей системы) sleep(1); >
Простой до невозможности код вызывает, всё же, несколько вопросов. Как его запустить? Как отслеживать его выполнение? Как его остановить?
Написание демона, часть первая. Создание скрипта для демонизации.
Как запустить php-демона
А как вообще запускают php-скрипты? Если это веб-приложение, то при помощи браузера и веб-сервера. Но этот вариант не подходит, ведь мы имеем дело с бесконечным скриптом, а время выполнения скриптов ограничены директивой max_execution_time в php.ini. Следовательно, бесконечный скрипт необходимо запускать через консоль, ведь тогда максимальное время его выполнения не учитывается. Примерно так выглядит команда запуска демона:
php -f /path/to/your/daemon.php
Остановить процесс демона можно так же, как и любой другой процесс:
kill xxxx
В приведённом примере xxxx — это и есть PID, идентификатор процесса.
Стоит отметить, что это не остановка, а именно «убийство» процесса демона. Дело в том, что работа скрипта будет прервана где попало, что не всегда и не всем подходит. По идее, в таком случае демона нужно останавливать где-то между его итерациями и уже не из консоли. К примеру, мы можем создать в базе данных или в каком-то файле на сервере заявку на остановку скрипта, а между итерациями демона проверять, нет ли такой заявки. Если заявка будет обнаружена, остановить цикл оператором break.
Система управления демонами
А что, если требуется создать и отслеживать сразу много демонов? К примеру, в упомянутом выше сервисе CheckTrust обработкой данных и проектов пользователей занимаются > 30 таких скриптов. Создавать и следить за ними из консоли очень неудобно — нужен более дружественный интерфейс.
Круто, да? 🙂 Как раз для создания подобной системы было бы неплохо иметь доступ к командной строке из php. Каждый демон — запись в базе данных, напротив которой можно проставить команду его запуска, а так же PID процесса. Следовательно, появляется возможность запускать, останавливать и отслеживать статус демонов прямо из веб-интерфейса. В итоге сами демоны у меня получились частью консольного приложения, а система управления ими — частью веб-приложения.
Поскольку система, показанная выше, заточена строго под CheckTrust, её код показывать я не буду. Зато скопирую сюда код php класса для управления процессами, который я использовал при её создании:
Единственный недостаток этого класса — то, что я уже описывал ваше — он останавливает процессы методом kill, но мне пока что этого достаточно 🙂 А вот и пример его использования:
// Запуск демона и получение PID (предполагается, что pid где-то сохраняется после запуска) $command = »; $process = new Process($command); $processId = $process->getPid(); // Проверка статуса демона $process = new Process(); $process->setPid($processId); $status = $process->status(); // возвращает true или false // Остановка демона $process = new Process(); $process->setPid($processId); $stopped = $process->stop(); // возвращает true или false
Подводя итог, хочу сказать, что это не единственно возможная и, вполне возможно, не самая оптимальная реализация демонов на php. К примеру, для многопроцессовых демонов существует крутое расширение PHP PCNTL. Кто-то, возможно, даже скажет, что для консольных приложений существуют совсем другие языки программирования. Но, как ни крути, у данной реализации есть неоспоримые преимущества:
- Она простая, как тапок. Демон — это просто бесконечный цикл, куда уж проще.
- Она совместима с веб-приложениями — являясь частью веб-сервиса, мои демоны опираются на существующие наработки, используют те же модели данных, классы и методы работы с ними.
- Она работает! Серьёзно — некоторые демоны у нас запущены уже несколько месяцев и, будучи грамотно написанными, не тупят, не зависают, не поглощают память.
Спасибо за внимание 🙂 Если у кого-нибудь возникнут вопросы или идеи, с удовольствием отвечу на них в комментариях.
Источник: www.mithrandir.ru
Создание демона Python с использованием Systemd
Недавно у меня возникла задача создать демон (фоновое приложение) реализованный на Python в системе Linux использующей Systemd . В поисках современного решения и родилась данная статья. Ранее для реализации демона выполнялась «демонизация» приложения Python, зачастую с помощью библиотеки python-daemon
(opens new window) . Даже была создана спецификация pep-3143
(opens new window) для реализации демонов. Но на текущий момент времени с использованием Systemd нет необходимости демонизировать наше Python приложение, достаточно корректно описать его запуск в юните. Для начала рассмотрим как работает Systemd .
Все операции указанные в данной статье выполнялись в окружении Linux Ubuntu 20, с установленным Python 3.8.
# Systemd Unit
- /usr/lib/systemd/system/ — юниты из установленных пакетов, такие как nginx, postgreee и др.
- /run/systemd/system — юниты созданные в runtime
- /etc/systemd/system — юиниты, созданные администратором, в основном пользовательские юниты должны храниться здесь.
Для создания юнита нам необходимо описать 3 секции: [Unit], [Service], [Install] Основные переменные блока [Unit]:
[Unit] Descripiton=Unit Descripion After=syslog.target After=network.target After=nginx.service After=mysql.service Requires=mysql.service Wants=redis.service
- Description — описание юнита
- After — указывает, что юнит должен быть запущен после группы указанных сервисов
- Requires — узказывает, что для запуска юнита требуется запущенный сервис mysql , запуск нашего сервиса выполняется паралллельно с требуемым ( mysql ), если требуемый не указан в After
- Wants — описательная переменная, показывающая, что для запуска сервиса желателен запущенный сервис redis
[Service] Type=simple PIDFile=/var/lib/service.pid WorkingDirectory=/var/www/myapp User=user Group=user Environment=STAGE_ENV=production OOMScoreAdjust=-100 ExecStart=/my_venv/bin/python my_app.py —start ExecStop=/my_venv/bin/python my_app.py —stop ExecReload=/my_venv/bin/python my_app.py —restart TimeoutSec=300 Restart=always
- Type — тип запуска: simple (по умолчанию) запускает службу незамедлительно при этом процесс не должен разветвляться, не подходит если другие службы зависят от очередности при запуске данной службы; forking — служба запускается однократно и процесс разветвляется с завершением родительского процесса; другие типы можно рассмотреть по ссылке ниже.
- PIDFile — позволяет задать место нахождения pid файла
- WorkingDirectory — указывает рабочий каталог приложения, если указан то ExecStart|Stop|Reload запускаются из этого каталога, т.е. my_app.py станет /var/www/myapp/my_app.py
- User, Group — соответственно пользователь и группа под которыми будет запущен сервис.
- Environment — переменные окружения
- OOOMSCoreAdjust — запрет на kill сервиса вследствие нехватки памяти и срабатывания механизма ООМ: -1000 полный запрет, -100 понижает вероятность.
- ExecStart|Stop|Reload — команды запуска, останова, перезагрузки сервиса, команда должна использовать абсолютный путь к исполняемому файлу.
- Timeout — время ожидания systemd отработки команд Start|Stop в сек.
- Restart — перезапуск сервиса если он упадет.
[Install] WantedBy=multi-user.target
- WantedBy — уровень запуска нашего сервиса, mulit-user.target или runlevel3.target соответствует runlevel=3 «Многопользовательский режим без графики»
Размещаем данный файл с указанными секциями в директории /etc/systemd/sysmtem/.service
Проверяем его статус:
systemctl -l status test_unit
Разрешаем его запуск:
systemd enable test_unit
Запускаем наш сервис
systemctl start test_unit
При внесении изменений в наш сервис перегружаем его:
systemctl daemon-reload
# Python приложение
Разобрав как работает Systemd можем приступить к созданию нашего сервиса. Для ознакомления создадим приложение Python которое будет выводить сообщения в log файл test_daemon.py :
import time import argparse import logging logger = logging.getLogger(‘test_daemon’) logger.setLevel(logging.INFO) formatstr = ‘%(asctime)s — %(name)s — %(levelname)s — %(message)s’ formatter = logging.Formatter(formatstr) def do_something(): «»» Здесь мы только лишь пишем сообщение в Log, но можем реализовать абсолютно любые задачи выполняемые в фоне. «»» while True: logger.info(«this is an INFO message») time.sleep(5) if __name__ == «__main__»: parser = argparse.ArgumentParser(description=»Example daemon in Python») # Мы можем заменить default или запускать приложение с указанием нахождения # log файла, через параметр -l /путь_к_файлу/файл.log parser.add_argument(‘-l’, ‘—log-file’, default=’/home/user/test_daemon.log’) args = parser.parse_args() fh = logging.FileHandler(args.log_file) fh.setLevel(logging.INFO) fh.setFormatter(formatter) logger.addHandler(fh) do_something()
Проверим работоспособность нашего скрипта:
python test_daemon.py
Проверим что log-файл создан и в него пишутся сообщения:
tail -f /home/user/test_daemon.log
[Unit] Description=Test daemon After=syslog.target [Service] Type=simple User=user Group=user WorkingDirectory=/home/user/ ExecStart=/usr/bin/python3 test_daemon.py [Install] WantedBy=multi-user.target
В данном юните следует изменить пользователя и группу User , Group на вашего пользователя. Также указать в качестве рабочего каталога WorkingDirectory абсолютный путь к директории где находится файл test_daemon.py.
В случае использования venv в параметре ExecStart следует указать путь к python внутри venv , т.е. заменть /usr/bin/python3 на /venv/bin/python
Проверяем статус нашего сервиса:
systemctl -l status test_daemon
Разрешаем его запуск:
systemd enable test_daemon
Запускаем наш сервис
systemctl start test_daemon
Проверим отображение данных в логе:
tail -f /home/user/test_daemon.log
Наш демон заработал, но осталась не решенной еще одна задача. Зачастую при остановке нашего демона требуется выполнить каие либо задачи (сохранить состояние приложения, отправить уведомление, выполнить очистку данных и т.п.), но на текущий момент при останове наш демон просто закрывается.
# Обработка сигналов завершения
В стандартной библиотеке Pyhton реализован модуль sygnal позволяющий обрабатывать сигналы UNIX-based операционной системы. Полный перечень сигналов можно посмотреть по команде:
kill — l
Демон Systemd при выполнении операции останова демона отправляет изначально сигнал 15 (SIGTERM) — нормальный останов процесса. По умолчанию если в течение 30 сек. не будет получен сигнал выхода приложения будет отправлен сигнал жесткого завершения процесса 9 (SIGKILL) .
Таким образом в нашем приложении необходимо предусмотреть обработчик сигнала 15 (SIGTERM) , как результат наш скрипт test_daemon.py примет следующий вид:
import time import argparse import logging import sys import signal logger = logging.getLogger(‘test_daemon’) logger.setLevel(logging.INFO) formatstr = ‘%(asctime)s — %(name)s — %(levelname)s — %(message)s’ formatter = logging.Formatter(formatstr) def terminate(signalNumber, frame): «»» Здесь мы можем обработать завершение нашего приложения Главное не забыть в конце выполнить выход sys.exit() «»» logger.info(f’Recieved signalNumber>’) sys.exit() def do_something(): «»» Здесь мы только лишь пишем сообщение в Log, но можем реализовать абсолютно любые задачи выполняемые в фоне. «»» while True: logger.info(«this is an INFO message») time.sleep(5) if __name__ == «__main__»: parser = argparse.ArgumentParser(description=»Example daemon in Python») # Мы можем заменить default или запускать приложение с указанием нахождения # log файла, через параметр -l /путь_к_файлу/файл.log parser.add_argument(‘-l’, ‘—log-file’, default=’/home/user/test_daemon.log’) args = parser.parse_args() signal.signal(signal.SIGTERM, terminate) fh = logging.FileHandler(args.log_file) fh.setLevel(logging.INFO) fh.setFormatter(formatter) logger.addHandler(fh) do_something()
Теперь при останове нашего демона:
systemctl start test_daemon