Проста демон система за Yii2
В тази статия ще се опитам да разкрия основните нюанси на внедряването на PHP демон системата и ще науча конзолните команди Yii2 да бъдат демонизирани.
През последните 3 години разработвам и развивам доста голям корпоративен портал за една група компании. Аз, както много други, се сблъсках с проблем, когато решението на задачата, която бизнесът изисква, не се вписва в никакви изчаквания. Направете отчет в excel за 300 хиляди реда, изпратете бюлетин за 1500 писма и т.н. Естествено, такива задачи трябва да се решават от фонови задачи, демони и crontabs. Като част от статията няма да сравнявам crons и демони, ние избрахме демони за решаване на такива проблеми. В същото време важно изискване за нас беше възможността да имаме достъп до всичко, което вече беше написано за бекенда, съответно демоните трябва да бъдат продължение на рамката Yii2. По същата причина готовите решения като phpDaemon не ни подхождаха.
Под изрязването има готово решение за внедряване на демони в Yii2, което измислих. Темата за демоните в PHP се появява със завидна редовност (едно, две, три, а момчетата от badoo дори ги рестартират, без да губят връзки). Може би моят велосипеден бърз начин за стартиране на демони на популярна рамка ще бъде полезен.
Някои основи
Отърваване от конзолата
Първо затворете стандартните потоци STDIN, STOUT, STDERR. Но PHP не може без тях, така че ще направи първия отворен поток стандартен, така че нека ги отворим в /dev/null.
След това разклоняваме процеса и правим разклонението основен процес. Процесът на донорство е завършен.
Безкраен цикъл и контрол
Мисля, че всичко се разбира с цикъла. Но необходимите механизми за контрол трябва да бъдат разгледани по-подробно.
Коригиране на вече стартирани процеси
Всичко е просто - следПри стартиране демонът поставя своя PID във файл със своето име и в края на работата си този файл се унищожава.
Работа с POSIX сигнали
Демонът трябва правилно да обработва сигнали от операционната система, т.е. при получаване на сигнал SIGTERM, той трябва да излезе грациозно. Това се постига чрез няколко неща: първо, дефинираме функция, която ще обработва получените сигнали:
Второ, във функцията за обработка на сигнали задаваме присвояването на някакво статично свойство на класа на true.
И трето, нашият безкраен цикъл вече не трябва да е толкова безкраен:
Сега, когато получи команда от операционната система, скриптът тихо ще завърши текущата итерация и ще излезе от цикъла.
Контролиране на изтичането на памет
За съжаление, ако демонът работи дълго време без рестартиране, паметта му започва да изтича. Интензивността на изтичането зависи от функциите, които използвате. От нашата практика най-силно „потекоха“ демони, които работят с отдалечени SOAP услуги чрез стандартния клас SoapClient. Така че трябва да наблюдавате това и периодично да ги рестартирате. Нека допълним нашия цикъл с условието за контрол на теча:
Къде е кодът за Yii?
Източниците са публикувани в Github - yii2-daemon, пакетът е достъпен и за инсталиране чрез композитор.
Пакетът се състои само от 2 абстрактни класа - базовият клас DaemonController и класът WatcherDaemonController.
DaemonController
Това е родителският клас за всички демони. Ето минимален пример за демон:
Функцията defineJobs() трябва да върне набор от задачи за изпълнение. По подразбиране се очаква да върне масив. Ако искате да се върнете, да речем MongoCursor, ще трябва също да замените defineJobExtractor(). Функцията doJob() трябва да получи onвъведете една задача за изпълнение, извършете необходимите операции с нея и маркирайте тази задача в източника като изпълнена, за да не падне втори път.
Възможни параметри и настройки:
- demonize- този параметър определя дали скриптът ще бъде демонизиран или ще се изпълнява като конзолно приложение. Параметърът е достъпен за настройка от конзолата: --demonize=1
- isMultiInstance и maxChildProcesses- определя дали демонът може да създава свои собствени копия и какъв е максималният брой от тях, които могат да работят едновременно. Тази функция ви позволява да изпълнявате няколко задачи едновременно. doJob ще се изпълнява в дъщерни процеси, а родителският процес ще делегира задачи само на своите деца и ще гарантира, че техният брой не надвишава разрешения максимум. Много е полезно, ако сървърът има достатъчно ресурси за изпълнение на няколко доста дълги задачи. По подразбиране това поведение е деактивирано. Параметрите са достъпни и от конзолата: --isMultiInstance=1 --maxChildProcesses=2
- memoryLimit— праг на потребление на памет от демона, ако демонът в режим на готовност надхвърли този праг, тогава той благородно ще извърши sippoku. Както вече беше посочено по-рано, за намаляване на размера на паметта, консумирана от демони в резултат на течове.
- sleep— време в секунди, за което демонът ще заспи между проверките за задачи. Демонът ще заспи само ако defineJob се върне празен и демонът няма да заспи, докато има задачи. Следователно defineJobs не трябва да връща статичен списък със задачи, в противен случай демонът ще ги вършее безкрайно и ще си почива.
- pidDir и logDirса пътища за съхраняване на регистрационни файлове и pids, поддържат Yii псевдоними. По подразбиране "@runtime/daemons/pids" и "@runtime/daemons/logs"
Проблем със загуба на връзка
Когато се извърши операция fork(), връзките, установени в родителския процес, спират да работят в дъщерните процеси. За да се избегне този проблем, функцията renewConnections() се извиква след всички разклонения. По подразбиране тази функция само свързва повторно Yii::$app->db, но можете да я замените, за да добавите други източници, с които искате да поддържате връзка в дъщерни процеси.
Сеч
Демоните преконфигурират стандартния регистратор на Yii за себе си. Ако не сте доволни от поведението по подразбиране, заменете функцията initLogger().
WatcherDaemonController
Това е почти готов демон-наблюдател. Задачата на този демон е да наблюдава други демони, да ги стартира и спира, ако е необходимо. Не може да стартира два пъти, така че можете спокойно да поставите стартирането му в crontab. За да започнете да го използвате, трябва да създадете папка daemons в конзолата/контролерите и да поставите класа на изгледа:
Трябва да се дефинира само една функция, getDaemonsList(), която ще върне списък с демони за наблюдение. В най-простата си форма това е масив, свързан с кода, но в този случай няма да можете да промените списъка в движение. Поставете списъка с демони в базата данни или отделен файл и го взимайте всеки път от там. В този случай наблюдателят ще може да активира или деактивира демона, без да се рестартира.
Заключение
В момента имаме над 50 демона, които изпълняват различни задачи, вариращи от изпращане на имейл съобщения до генериране на отчети и актуализиране на данни между различни системи.
Демоните работят с различни източници на задачи - MySQL, RabbitMQ и дори отдалечени уеб услуги. Полетът е нормален. Определено php демоните не съвпадат с Go демоните. Но високата скорост на разработка, възможността за повторно използване на вече написан код и липсата на необходимост екипът да се учи на друг език надделяват над минусите.
И тук можете да получите грант за тестов период на Yandex.Cloud. Необходимо е само да въведете "Habr" в полето "секретна парола".