remove - ¿La mejor forma de administrar un script php de larga duración?
strip_tags wordpress (14)
Tengo un script PHP que toma mucho tiempo (5-30 minutos) para completarse. Por si acaso importa, la secuencia de comandos utiliza curl para raspar datos de otro servidor. Esta es la razón por la que lleva tanto tiempo; tiene que esperar a que cada página se cargue antes de procesarla y pasar a la siguiente.
Quiero poder iniciar el script y dejarlo funcionar hasta que esté listo, lo que establecerá un indicador en una tabla de base de datos.
Lo que necesito saber es cómo poder finalizar la solicitud http antes de que la secuencia de comandos termine de ejecutarse. Además, ¿es un script php la mejor manera de hacer esto?
Ciertamente, se puede hacer con PHP, sin embargo, NO debe hacer esto como una tarea en segundo plano: el nuevo proceso debe disocarse del grupo de procesos donde se inicia.
Dado que la gente sigue dando la misma respuesta incorrecta a esta pregunta frecuente, he escrito una respuesta más completa aquí:
http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html
De los comentarios:
La versión corta es
shell_exec(''echo /usr/bin/php -q longThing.php | at now'');
pero las razones por las cuales son un poco largas para incluirlas aquí.
Estoy de acuerdo con las respuestas que dicen que esto debe ejecutarse en un proceso en segundo plano. Pero también es importante que informe sobre el estado para que el usuario sepa que el trabajo se está realizando.
Al recibir la solicitud de PHP para iniciar el proceso, puede almacenar en una base de datos una representación de la tarea con un identificador único. Luego, inicie el proceso de eliminación de pantalla, pasando el identificador único. Informe a la aplicación de iPhone que la tarea se ha iniciado y que debe verificar una URL especificada, que contiene la nueva ID de tarea, para obtener el estado más reciente. La aplicación de iPhone ahora puede sondear (o incluso "encuestar largo") esta URL. Mientras tanto, el proceso en segundo plano actualizaría la representación de la base de datos de la tarea, ya que funcionó con un porcentaje de finalización, un paso actual o cualquier otro indicador de estado que desee. Y cuando haya terminado, establecerá una bandera completa.
He hecho cosas similares con Perl, double fork () y separación del proceso principal. Todo el trabajo de búsqueda http debe hacerse en proceso bifurcado.
La manera rápida y sucia sería usar la función ignore_user_abort
en php. Esto básicamente dice: no importa lo que haga el usuario, ejecute este script hasta que finalice. Esto es algo peligroso si se trata de un sitio público (porque es posible que termine teniendo versiones 20 ++ del script ejecutándose al mismo tiempo si se inicia 20 veces).
La forma "limpia" (al menos en mi humilde opinión) es establecer un indicador (en el DB por ejemplo) cuando desee iniciar el proceso y ejecutar un cronjob cada hora (más o menos) para verificar si ese indicador está configurado. Si está configurado, el script de ejecución larga se inicia; si NO está configurado, no sucede nada.
Me doy cuenta de que esta es una pregunta bastante antigua, pero me gustaría darle una oportunidad. Esta secuencia de comandos intenta abordar tanto la llamada de inicio inicial para terminar rápidamente y cortar la carga pesada en trozos más pequeños. No he probado esta solución.
<?php
/**
* crawler.php located at http://mysite.com/crawler.php
*/
// Make sure this script will keep on runing after we close the connection with
// it.
ignore_user_abort(TRUE);
function get_remote_sources_to_crawl() {
// Do a database or a log file query here.
$query_result = array (
1 => ''http://exemple.com'',
2 => ''http://exemple1.com'',
3 => ''http://exemple2.com'',
4 => ''http://exemple3.com'',
// ... and so on.
);
// Returns the first one on the list.
foreach ($query_result as $id => $url) {
return $url;
}
return FALSE;
}
function update_remote_sources_to_crawl($id) {
// Update my database or log file list so the $id record wont show up
// on my next call to get_remote_sources_to_crawl()
}
$crawling_source = get_remote_sources_to_crawl();
if ($crawling_source) {
// Run your scraping code on $crawling_source here.
if ($your_scraping_has_finished) {
// Update you database or log file.
update_remote_sources_to_crawl($id);
$ctx = stream_context_create(array(
''http'' => array(
// I am not quite sure but I reckon the timeout set here actually
// starts rolling after the connection to the remote server is made
// limiting only how long the downloading of the remote content should take.
// So as we are only interested to trigger this script again, 5 seconds
// should be plenty of time.
''timeout'' => 5,
)
));
// Open a new connection to this script and close it after 5 seconds in.
file_get_contents(''http://'' . $_SERVER[''HTTP_HOST''] . ''/crawler.php'', FALSE, $ctx);
print ''The cronjob kick off has been initiated.'';
}
}
else {
print ''Yay! The whole thing is done.'';
}
Me gustaría proponer una solución que sea un poco diferente de la de symcbean, principalmente porque tengo el requisito adicional de que el proceso de ejecución prolongada debe ejecutarse como otro usuario, y no como usuario de apache / www-data.
Primera solución que usa cron para sondear una tabla de tareas en segundo plano:
- La página web PHP se inserta en una tabla de tareas en segundo plano, indica ''ENVIADO''
- cron se ejecuta una vez cada 3 minutos, utilizando otro usuario, ejecutando la secuencia de comandos PHP CLI que verifica las filas "ENVIADAS" en la tabla de tareas en segundo plano
- PHP CLI actualizará la columna de estado en la fila en ''PROCESAMIENTO'' y comenzará el procesamiento, una vez que se complete se actualizará a ''COMPLETADO''
Segunda solución que utiliza Linux inotify facility:
- La página web de PHP actualiza un archivo de control con los parámetros establecidos por el usuario, y también proporciona una identificación de la tarea
- shell script (como un usuario que no es www) que ejecuta inotifywait esperará a que se escriba el archivo de control
- después de escribir el archivo de control, se generará un evento close_write y el script de shell continuará
- shell script ejecuta PHP CLI para hacer el proceso de larga ejecución
- PHP CLI escribe el resultado en un archivo de registro identificado por la identificación de la tarea o, alternativamente, actualiza el progreso en una tabla de estado
- La página web de PHP puede sondear el archivo de registro (basado en la identificación de la tarea) para mostrar el progreso del proceso de larga ejecución, o también puede consultar la tabla de estado
Alguna información adicional se puede encontrar en mi publicación: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
No, PHP no es la mejor solución.
No estoy seguro acerca de Ruby o Perl, pero con Python podría volver a escribir el raspador de su página para que tenga varios subprocesos y probablemente se ejecute al menos 20 veces más rápido. Escribir aplicaciones con varios subprocesos puede ser un desafío, pero la primera aplicación de Python que escribí fue el raspador de páginas mutlti-threaded. Y puede simplemente llamar al script de Python desde su página PHP utilizando una de las funciones de ejecución del shell.
PHP puede o no ser la mejor herramienta, pero usted sabe cómo usarla, y el resto de su aplicación está escrita utilizándola. Estas dos cualidades, combinadas con el hecho de que PHP es "lo suficientemente bueno", son una buena razón para usarlo, en lugar de Perl, Ruby o Python.
Si su objetivo es aprender otro idioma, elija uno y úselo. Cualquier idioma que ha mencionado hará el trabajo, no hay problema. Me gusta Perl, pero lo que te gusta puede ser diferente.
Symcbean tiene algunos buenos consejos sobre cómo administrar los procesos en segundo plano en su enlace.
En resumen, escriba un script CLI PHP para manejar los bits largos. Asegúrese de que informa el estado de alguna manera. Haga una página php para manejar actualizaciones de estado, ya sea usando AJAX o métodos tradicionales. Su script kickoff comenzará el proceso ejecutándose en su propia sesión y devolverá la confirmación de que el proceso está en marcha.
Buena suerte.
Puede enviarlo como una solicitud XHR (Ajax). Los clientes no suelen tener tiempo de espera para XHR, a diferencia de las solicitudes HTTP normales.
Puede usar exec o system para comenzar un trabajo en segundo plano, y luego hacer el trabajo en eso.
Además, hay mejores enfoques para raspar la web que el que estás usando. Puede usar un enfoque enhebrado (múltiples hilos que hacen una página a la vez), o uno usando un eventloop (un hilo haciendo varias páginas a la vez). Mi enfoque personal con Perl sería usar AnyEvent::HTTP .
ETA: symcbean explicó cómo separar el proceso de fondo http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html .
Sí, puedes hacerlo en PHP. Pero además de PHP, sería conveniente usar un administrador de colas. Aquí está la estrategia:
Divida su gran tarea en tareas más pequeñas. En su caso, cada tarea podría estar cargando una sola página.
Envía cada pequeña tarea a la cola.
Ejecute sus trabajadores de cola en alguna parte.
El uso de esta estrategia tiene las siguientes ventajas:
Para tareas de larga ejecución, tiene la capacidad de recuperarse en caso de que se produzca un problema fatal en el medio de la ejecución, sin necesidad de comenzar desde el principio.
Si sus tareas no tienen que ejecutarse secuencialmente, puede ejecutar varios trabajadores para ejecutar tareas simultáneamente.
Usted tiene una variedad de opciones (estas son solo algunas):
- RabbitMQ ( https://www.rabbitmq.com/tutorials/tutorial-one-php.html )
- ZeroMQ ( http://zeromq.org/bindings:php )
- Si está utilizando el marco de trabajo de Laravel, las colas están incorporadas ( https://laravel.com/docs/5.4/queues ), con controladores para AWS SES, Redis, Beanstalkd
Use un proxy para delegar la solicitud.
lo que SIEMPRE uso es una de estas variantes (porque los diferentes sabores de Linux tienen reglas diferentes sobre el manejo de la salida / algunos programas tienen resultados diferentes):
Variante I @exec (''./ myscript.php / 1> / dev / null / 2> / dev / null &'');
Variante II @exec (''php -f myscript.php / 1> / dev / null / 2> / dev / null &'');
Variante III @exec (''nohup myscript.php / 1> / dev / null / 2> / dev / null &'');
Puede que tengas que instalar "nohup". Pero, por ejemplo, cuando estaba automatizando conversiones de video FFMPEG, la interfaz de salida de alguna manera no se manejaba al 100% mediante la redirección de las secuencias de salida 1 y 2, así que usé nohup Y redirigí la salida.
si tienes un script largo, divide el trabajo de la página con la ayuda del parámetro de entrada para cada tarea (cada página actúa como hilo), es decir, si la página tiene 1 lac product_keywords ciclo de proceso largo, en lugar de hacer lógica para una palabra clave y pasa esta palabra clave de magic o cornjobpage.php (en el siguiente ejemplo)
y para el trabajador de segundo plano, creo que debería probar esta técnica, será útil llamar tantas páginas como desee, todas las páginas se ejecutarán de forma independiente, sin esperar a que cada respuesta de página sea asincrónica.
cornjobpage.php // mainpage
<?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.
?>
<?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
?>
PD: si desea enviar parámetros de url como bucle, siga esta respuesta: https://.com/a/41225209/6295712