Запуск PHP скриптов в фоновом режиме

Когда проект растет, появляются ресурсоемкие задачи обработки данных. Например, разбор xlsx (эксель) прайса для обновления цен, или ресайз большой фотографии. Случаи могут быть самыми разнообразными, когда мы не хотим, чтобы пользователь ждал, пока наш скрипт отработает.

В данном посте расскажу, как можно запускать фоновое выполнение PHP скриптов. Следует отметить, что без использования VPS (Virtual Private Server), то есть на обычном шаред хостинге, такой способ не сработает. Строго говоря, диспетчер создавался под Linux, хотя не исключена корректная работа и на FreeBSD. Даже скорее всего будет работать тоже.

В самом диспетчере ничего сложного нет, он скорее имеет больше заморочек по администрированию. Давайте сначала разберем, из чего он состоит со стороны ОС, какие используются команды:

ps -C php -o pid=,command=

Здесь мы запрашиваем список процессов с именем php. Остальные параметры влияют на отображение результатов, т.е. мы хотим видеть pid, и непосредственно полную команду, включая параметры. Пример вывода этой команды:

12769 php /path/to/1.php /path/to/price.xlsx

Таким образом, мы проверяем, запущена ли уже такая задача, и если запущена, то по умолчанию повторный запуск не производится. Если все же требуется параллельно запустить еще один экземпляр с точно такими же параметрами, можно это указать явным способом.

php -d max_execution_time=300 –f /pathto/1.php /pathto/price.xlsx > /dev/null 2>&1 & echo $!

Здесь мы запускаем php, причем указываем максимальное время выполнения в 300 секунд, чтобы наши фоновые процессы не превратились в бесконечные. Простая мера предосторожности, не более. Далее идет путь к скрипту, потом параметры, если есть необходимость. Перенаправляем вывод вместо stdout в «черную дыру», 2>&1 означает, что мы так же направляем вывод ошибок туда же. Кстати, можно перенаправить в файл через >>, в целях дебаггинга. Но советую отлаживать скрипты перед тем как их щапускать в фоне. Самое главное, последняя часть: & уходим в background и echo $! Как раз выводим (возвращаем) PID только что созданного процесса.

Это все, теперь сам код диспетчера и пример использования:

class backgrounder
{
    public function launch($path = null, $timeout = 300, $once = true)
    {
        if (! file_exists(preg_replace('/^(\S+).*/', '$1', $path))) {
            throw new Exception('No such file to launch');
        }
        
        if ($once === true) {
            exec('ps -C php -o pid=,command=', $output);
            foreach ($output as $row) {
                preg_match('/-f (.*)/', $row, $result);
                if (!empty($result[1]) && $result[1] == $path) {
                    return false;
                }
            }
        }
        
        return exec('php -d max_execution_time=' 
                     . $timeout . ' -f ' . $path 
                     . ' > /dev/null 2>&1 & echo $!', $output);
    }
    
    public function isRunning($pid)
    {
        exec('ps -p ' . $pid . ' -o command=', $output);
        
        if (empty($output)) {
            return false;
        }
        
        if (preg_match('/php -d max_execution_time=/', $output[0])) {
            return true;
        }
        
        return false;
    }
    
}

Допустим у нас есть php скрипт, который принимает в параметрах путь до файла, читает его и складывает содержимое в другой файл. Причем делает это очень долго (заменим на sleep 😉 )

//copyman.php

//В массиве $argv все переданные параметры вызова
if (empty($argv[1])) {
    exit();
}

sleep(30);

$content = file_get_contents($argv[1]);
file_put_contents('ourfile.txt', $content);

А вот, как запускать его в фоновом режиме:

//index.php
require_once 'backgrounder.php';

$bg = new backgrounder();

$pid = $bg->launch(__DIR__ . '/copyman.php exampledata.txt');
echo $pid;

Метод launch может принимать 3 параметра. Первый это путь к файлу-скрипту со всеми параметрами; второй – максимальное время выполнения (по умолчанию 300 секунд) и третий разрешено ли запускать копии, то есть точно такие же процессы с идентичными параметрами.

Обратите внимание, в переменную $pid мы вернули идентификатор процесса. Его можно где-нибудь сохранить, например, в куках пользователя, и при следующем открытии страницы узнать, закончилось ли выполнение задачи. Для этого метод isRunning

$gb->isRunning(13041) //PID

В ответ true или false соответственно.

Как обычно, предлагаю скачать ↓ демонстрационную сборку ↓

Добавить комментарий