Як я можу запустити скрипт PHP у фоновому режимі після надсилання форми?


104

Проблема У
мене є форма, яка при надходженні запустить базовий код для обробки поданої інформації та вставки її в базу даних для відображення на веб-сайті сповіщень. Крім того, у мене є список людей, які підписалися на отримання цих сповіщень електронною поштою та SMS-повідомленнями. Цей список є тривіальним як момент (всього лише близько 150), однак для того, щоб пройти цикл по всій таблиці передплатників і відправити 150+ електронних листів, достатньо, щоб зайняти хвилину вгору. (Електронні листи надсилаються індивідуально, як цього вимагають системні адміністратори нашого сервера електронної пошти через масову політику електронної пошти.)

Протягом цього часу особа, яка опублікувала попередження, майже хвилину сидітиме на останній сторінці форми без жодного позитивного підкріплення того, що їх повідомлення розміщується. Це призводить до інших потенційних проблем, все, що є можливими рішеннями, які я вважаю, є менш ідеальними.

  1. По-перше, плакат може подумати, що сервер відстає і знову натиснути кнопку "Надіслати", в результаті чого сценарій запускається або запускається двічі. Я міг би вирішити це за допомогою JavaScript для відключення кнопки та заміни тексту, щоб сказати щось на кшталт "Обробка ...", однак це менш ніж ідеально, оскільки користувач все ще буде застряг на сторінці протягом тривалості виконання сценарію. (Крім того, якщо JavaScript відключений, ця проблема все ще існує.)

  2. По-друге, плакат може закрити вкладку чи браузер передчасно після надсилання форми. Сценарій буде працювати на сервері до тих пір, поки він не спробує записати його назад у браузер, однак якщо користувач перегляне будь-яку сторінку в нашому домені (поки сценарій все ще працює), браузер зависне завантажувати сторінку, поки сценарій не закінчиться . (Це відбувається лише тоді, коли вкладка чи вікно веб-переглядача закриті, а не вся програма браузера.) Все ж це менш ніж ідеально.

(Можливо) Рішення.
Я вирішив, що хочу вивести частину сценарію "електронною поштою" в окремий файл, до якого я можу зателефонувати після публікації повідомлення. Спочатку я думав розмістити це на сторінці підтвердження після успішного розміщення повідомлення. Однак користувач не знатиме, що цей сценарій працює, і будь-які аномалії їм не будуть помітні; Цей сценарій не може вийти з ладу.

Але що робити, якщо я можу запустити цей скрипт як фоновий процес? Отже, моє запитання таке: Як я можу виконати скрипт PHP для запуску як фонової послуги та запустити повністю незалежно від того, що зробив користувач на рівні форми?

РЕДАКТУВАТИ : Цього не можна поширити. Він повинен запускатися з моменту подання форми. Це сповіщення з високим пріоритетом. Крім того, системні адміністратори, що працюють на наших серверах, забороняють кронам працювати більше 5 хвилин.


Запустіть роботу cron та попередити користувача на іншій сторінці, що їх запит обробляється чи щось подібне.
Еван Мулавський

1
Ви хочете запускати його регулярно (щодня або щогодини) або після подачі? Я думаю, ви можете використовувати php, exec()але я ніколи цього не пробував.
Симон

2
Я просто використовую ajaxта вставляю sleep()з тимчасовим інтервалом всередині циклу (я використовую foreach у моєму випадку) для запуску фонового процесу. Він прекрасно працює на моєму сервері. Не впевнений у інших. Я також додаю ignore_user_abort()і set_time_limit()(з розрахунковим кінцевим часом) перед циклом, щоб переконатися, що скрипт не буде зупинений сервером, яким я користуюся, навіть згідно мого тесту, сценарій, що виконує завдання без ignore_user_abort()і set_time_limit().
Арі

Я бачив, що ви вже придумали рішення. Я використовую gearmanphp для вирішення фонових завдань. Це ідеально підходить для масштабування, якщо ви стикалися з великою обробкою часу та великими навантаженнями. Ви повинні поглянути на це.
Андре Гарсія

Дотримуйтесь цієї відповіді на [stackoverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Відповіді:


89

Зробив кілька експериментів execі shell_execя виявив рішення, яке прекрасно працювало! Я вирішив використовувати, shell_execщоб я міг реєструвати кожен процес сповіщення, який трапляється (або ні). ( shell_execповертається як рядок, і це було простіше, ніж використовувати exec, призначаючи вихідну змінну і потім відкриваючи файл для запису.)

Я використовую наступний рядок, щоб викликати скрипт електронної пошти:

shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

Важливо помітити &в кінці команди (на що вказував @netcoder). Ця команда UNIX запускає процес у фоновому режимі.

Додаткові змінні, оточені окремими лапками після шляху до сценарію, встановлюються як $_SERVER['argv']змінні, які я можу викликати у своєму сценарії.

Потім скрипт електронної пошти виводиться в мій файл журналу за допомогою >>і виведе щось подібне:

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)

Дякую. Це врятувало мені життя.
Ліам Бейлі

Що це $post_idза шлях до сценарію php?
Perocat

@Perocat - це змінна, яку ви надсилаєте до сценарію. У цьому випадку він надсилає ідентифікатор, який буде параметром для викликаного сценарію.
Chique

Я сумніваюся , що це дійсно працює відмінно, см stackoverflow.com/questions/2212635 / ...
symcbean

1
Там в очікуванні редагування пропозицію про втечу $post_id; Я волів би кинути його прямо на номер: (int) $post_id. (Закликаючи вашу увагу вирішити, що краще.)
Al.G.

19

На серверах Linux / Unix ви можете виконати завдання у фоновому режимі, використовуючи proc_open :

$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

&Є важливим тут небагато. Сценарій продовжиться, навіть якщо початковий сценарій закінчився.


Це працює, коли я не дзвоню, proc_closeколи завдання виконано. proc_closeзмушує сценарій зависати приблизно від 15 до 25 секунд. Мені потрібно буде вести журнал, використовуючи інформацію про трубу, тому я повинен відкрити файл, в який потрібно записати, закрити його, а потім закрити Proc-з'єднання. Тож, на жаль, це рішення для мене не працює.
Майкл Ірігойен

3
Звичайно, ти не дзвониш proc_close, у цьому справа! І так, ви можете передавати файл у файл proc_open. Дивіться оновлену відповідь.
netcoder

@netcoder: що робити, якщо я не хочу зберігати результат у жодному файлі. які зміни потрібні в stdout?
naggarwal11

9

З усіх відповідей жоден не вважав смішно простою функцією fastcgi_finish_request , що при виклику видаляє весь залишився вихід до браузера і закриває сеанс Fastcgi та HTTP-з'єднання, дозволяючи сценарію працювати у фоновому режимі.

Приклад:

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
fastcgi_finish_request(); // The user is now disconnected from the script

// do stuff with received data,

це саме те, що я шукав, в shell_exec я повинен якось перенести розміщені дані у виконаний сценарій, який сам по собі є трудомістким завданням у моєму випадку, а також робить речі дуже складними.
Аурангзеб

7

PHP exec("php script.php")може це зробити.

З посібника :

Якщо програма запускається з цією функцією, для того, щоб вона продовжувала працювати у фоновому режимі, висновок програми повинен бути перенаправлений у файл або інший вихідний потік. Якщо цього не зробити, PHP зависне до завершення виконання програми.

Отже, якщо ви перенаправляєте вихід на файл журналу (що все-таки є хорошою ідеєю), ваш скрипт виклику не зависне, а ваш скрипт електронної пошти працюватиме в bg.


1
Дякую, але це не вийшло. Сценарій все ще "висить", поки він обробляє другий файл.
Майкл Ірігойен

Подивіться на коментар php.net/manual/en/function.exec.php#80582 Здається, це тому, що Safe_mode увімкнено. Існує рішення унікс.
Симон

На жаль, безпечний режим не вмикається під час встановлення. Дивно, що він все ще висить.
Майкл Ірігойен

6

І чому б не зробити запит HTTP на скрипт і ігнорувати відповідь?

http://php.net/manual/en/function.httprequest-send.php

Якщо ви зробите свій запит на сценарій, вам потрібно зателефонувати своєму веб-серверу, він запустить його у фоновому режимі, і ви зможете (у головному сценарії) показати повідомлення, що повідомляє користувачеві про те, що сценарій працює.


4

Як щодо цього?

  1. Ваш PHP-скрипт, який містить форму, зберігає прапор або якесь значення у базі даних або файлі.
  2. Другий скрипт PHP періодично опитує це значення, і якщо воно встановлено, воно запускає скрипт електронної пошти синхронно.

Цей другий скрипт PHP повинен бути налаштований як запуск.


Дякую, але я оновив оригінальне запитання. Cron's - це не варіант у цьому випадку.
Майкл Ірігойен

4

Як я знаю, ти не можеш це зробити простим способом (див. Fork exec тощо (не працює під Windows)), може бути, ти можеш змінити підхід, використовувати тло браузера, розміщуючи форму в ajax, так що якщо публікація ще робота у вас немає часу на очікування.
Це може допомогти, навіть якщо вам доведеться зробити довгу детальну розробку.

Щодо надсилання пошти, завжди пропонується використовувати шпулер, це може бути локальний та швидкий сервер smtp, який приймає ваші запити та передає їх у справжній MTA або вкладає все в БД, ніж використовувати крон, який розкручує чергу.
Cron може знаходитись на іншій машині, що викликає монтер як зовнішній URL:

* * * * * wget -O /dev/null http://www.example.com/spooler.php

3

Робота з фоновим хроном звучить як гарна ідея для цього.

Вам буде потрібен ssh доступ до машини, щоб запустити скрипт як cron.

$ php scriptname.php запустити його.


1
немає необхідності в ssh. Багато хостів забороняють ssh, але мають підтримку cron.
Тесон

Дякую, але я оновив оригінальне запитання. Cron's - це не варіант у цьому випадку.
Майкл Ірігойен

3

Якщо ви можете отримати доступ до сервера через ssh і можете запускати власні сценарії, ви можете зробити простий сервер fifo за допомогою php (хоча вам доведеться перекомпілювати php з posixпідтримкою fork).

Сервер можна записати будь-що насправді, напевно, ви можете легко зробити це в python.

Або найпростішим рішенням буде надсилання HttpRequest і не зчитування повернених даних, але сервер може знищити сценарій, перш ніж він закінчить обробку.

Приклад сервера:

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

Приклад клієнта:

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>

3

Найпростіший спосіб запустити скрипт PHP у фоновому режимі

php script.php >/dev/null &

Сценарій буде працювати у фоновому режимі, і сторінка також швидше дістанеться до сторінки дій.


2

Якщо припустити, що ви працюєте на платформі * nix, використовуйте cronта phpвиконуваний файл.

Редагувати:

Існує досить багато запитань із запитом "запустити php без cron" на SO. Ось один:

Плануйте сценарії без використання CRON

Однак, відповідь exec () звучить дуже перспективно :)


Дякую, але я оновив оригінальне запитання. Cron's - це не варіант у цьому випадку.
Майкл Ірігойен

2

Якщо ви перебуваєте в Windows, дослідженнях proc_openабо popen...

Але якщо ми знаходимось на тому ж сервері "Linux" під керуванням cpanel, це правильний підхід:

#!/usr/bin/php 
<?php
$pid = shell_exec("nohup nice php -f            
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,        
 // or fgets
}
?>

Не використовуйте fork()або curlякщо ви сумніваєтесь, що можете з ними впоратися, це як зловживання вашим сервером

Нарешті, у script.phpфайлі, який називається вище, врахуйте це, переконайтеся, що ви написали:

<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php

//Code to be tested on background

ob_flush(); flush(); 
//this two do the output process if you need some.        
//then to make all the logic possible


str_repeat(" ",1500); 
//.for progress bars or loading images

sleep(2); //standard limit

?>

Вибачте за те, що згодом ви редагували його за використання свого мобільного телефону, і справді це набагато важче, ніж писати сценарій exec (). Lol;)
я ArbZ

2

для фонового працівника, я думаю, ви повинні спробувати цю техніку, це допоможе зателефонувати стільки сторінок, які вам подобаються, а всі сторінки будуть працювати одночасно незалежно, не чекаючи, коли кожна відповідь сторінки буде асинхронною.

formgery_page.php

     <?php

    post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
    //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
    //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
    //call as many as pages you like all pages will run at once //independently without waiting for each page response as asynchronous.

  //your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response     
                ?>
                <?php

                /*
                 * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
                 *  
                 */
                function post_async($url,$params)
                {

                    $post_string = $params;

                    $parts=parse_url($url);

                    $fp = fsockopen($parts['host'],
                        isset($parts['port'])?$parts['port']:80,
                        $errno, $errstr, 30);

                    $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                    $out.= "Host: ".$parts['host']."\r\n";
                    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                    $out.= "Content-Length: ".strlen($post_string)."\r\n";
                    $out.= "Connection: Close\r\n\r\n";
                    fwrite($fp, $out);
                    fclose($fp);
                }
                ?>

testpage.php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
//here do your background operations it will not halt main page
    ?>

PS: якщо ви хочете надіслати параметри url як цикл, дотримуйтесь цієї відповіді: https://stackoverflow.com/a/41225209/6295712


0

У моєму випадку у мене є 3 парами, один з них - рядок (mensaje):

exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");

У своєму Process.php у мене є такий код:

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];

0

Це працює для мене. tyr це

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);

що робити, якщо у мене є значення пост? з форми, і я надсилаю її через ajax та serialize()функцію. наприклад, у мене є index.php форма і я надсилаю значення через ajax до index2.php. Я хочу виконати надсилання даних у index2.php у фоновому режимі, що ви пропонуєте? спасибі
khalid JA

0

Використовуйте Amphp для виконання завдань паралельно та асинхронно.

Встановіть бібліотеку

composer require amphp/parallel-functions

Зразок коду

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';

Для вашого випадку використання Ви можете зробити щось на кшталт нижче

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'a@example.com',
    'b@example.com',
    'c@example.com',
], function ($to) {
    return send_mail($to);
}));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.