2012-01-17 18 views
7

Tengo el siguiente problema con mi servidor VPS.Ejecutando scripts PHP concurrentes

Tengo un script PHP de larga ejecución que envía grandes archivos al navegador. Se hace algo como esto:

<?php 
header("Content-type: application/octet-stream"); 
readfile("really-big-file.zip"); 
exit(); 
?> 

Esto lee básicamente el archivo del sistema de archivos del servidor y lo envía al navegador. No puedo usar enlaces directos (y dejar que Apache sirva el archivo) porque hay una lógica comercial en la aplicación que debe aplicarse.

El problema es que, mientras se está ejecutando la descarga, el sitio no responde a otras solicitudes.

+0

No es que este sea el problema, pero al servir archivos de gran tamaño, siempre debe llamar a 'set_time_limit (0);'. No debería hacer ninguna diferencia para usted en este momento, pero le anticipará problemas potenciales que puede experimentar si mueve esto en algún momento a una * * plataforma Windows. – DaveRandom

+1

¿Cómo has descubierto el problema? ¿Está probando esto haciendo múltiples solicitudes desde la misma máquina? ¿Y estás usando sesiones? – DaveRandom

+0

@DaveRandom Me di cuenta del problema cuando traté de descargar varios archivos (se pusieron en cola para su descarga). Estoy usando sesiones, solo intenté y parece que esta restricción no afecta otras sesiones. Gracias por sus comentarios. Investigaré más a fondo ahora. –

Respuesta

30

El problema que está experimentando está relacionado con el hecho de que está utilizando sesiones. Cuando un script tiene una sesión en ejecución, bloquea el archivo de sesión para evitar escrituras simultáneas que pueden dañar los datos de la sesión. Esto significa que las solicitudes múltiples del mismo cliente, que utilizan la misma ID de sesión, no se ejecutarán simultáneamente, se pondrán en cola y solo se podrán ejecutar de a una por vez.

Múltiples usuarios no experimentarán este problema, ya que utilizarán diferentes ID de sesión. Esto no significa que no tenga un problema, ya que es posible que desee acceder al sitio mientras se descarga un archivo, o configurar varios archivos a la vez.

La solución es realmente muy simple: llame al session_write_close() antes de comenzar a imprimir el archivo. Esto cerrará el archivo de sesión, liberará el bloqueo y permitirá la ejecución de más solicitudes simultáneas.

+0

Gracias, ese era el problema. –

+0

Buen punto y +1 – Lion

+0

Simplemente excelente. ¡Me hiciste el día! – Claudix

1

¿De qué manera el servidor no responde a otras solicitudes? ¿Es "Esperando example.com ..." o muestra un error de algún tipo?

Hago algo similar, pero sirvo el archivo en trozos, lo que le da un descanso al sistema de archivos mientras el cliente acepta y descarga un trozo, que es mejor que ofrecer todo al mismo tiempo, lo cual es bastante exigente. sistema de archivos y todo el servidor.

EDITAR: Si bien no es la respuesta a esta pregunta, asker preguntó acerca de leer un archivo fragmentado. Esta es la función que uso. Proporcione la ruta completa al archivo.

function readfile_chunked($file_path, $retbytes = true) 
{ 
$buffer = ''; 
$cnt = 0; 
$chunksize = 1 * (1024 * 1024); // 1 = 1MB chunk size 
$handle = fopen($file_path, 'rb'); 
if ($handle === false) { 
    return false; 
} 
while (!feof($handle)) { 
    $buffer = fread($handle, $chunksize); 
    echo $buffer; 
    ob_flush(); 
    flush(); 
    if ($retbytes) { 
     $cnt += strlen($buffer); 
    } 
} 
    $status = fclose($handle); 
    if ($retbytes && $status) { 
     return $cnt; // return num. bytes delivered like readfile() does. 
} 
    return $status; 
} 
+0

Las nuevas solicitudes están esperando completar la descarga. No estoy seguro de cómo puedo implementar la descarga de fragmentos en PHP. –

+0

Hola, Emil M. He agregado la función que uso para leer el archivo en trozos. –

+0

Acabo de darles la respuesta, así que déjame explicarte. readfile no lee el contenido del archivo en la memoria y luego lo escribe en stdout. No, llama a la función interna '_php_stream_passthru()' que es un bucle C similar al tuyo, excepto que no hace las descargas ya que la configuración INI 'output_buffering' logrará esto de todos modos. Y 'zlib.output_compression' etc. debe ser falso para generar contenido ya comprimido. Su sugerencia es simplemente agregar complejidad sin ningún beneficio. – TerryE

0

He intentado diferentes enfoques (leer y enviar los archivos en pequeños trozos [véanse los comentarios sobre readfile en PHP doc], usando peras HTTP_Download) pero siempre se encontró con problemas de rendimiento cuando los archivos están recibiendo grandes.

Hay un mod de Apache donde puede hacer su lógica de negocio y luego delegar la descarga a Apache. La descarga no estará públicamente disponible. Creo que esta es la solución más elegante para el problema.

Más información:

2

Probablemente, la configuración de su servidor no sea el único lugar donde debe comprobar.

Intente hacer una solicitud desde su navegador como de costumbre y luego haga otra desde otro cliente.

Cualquiera wget desde la misma máquina u otro navegador en una máquina diferente.

0

Pasa lo mismo y no estoy usando sesiones. session.auto_start está establecido en 0 Mi script de ejemplo solo ejecuta "sleep (5)" y al agregar "session_write_close()" al principio no resuelve el problema.

0

Revise su archivo httpd.conf. Tal vez tenga "KeepAlive On" y es por eso que su segunda solicitud se suspende hasta que se complete la primera. En general, su script PHP no debe permitir que los visitantes esperen durante mucho tiempo. Si necesita descargar algo grande, hágalo en una solicitud interna separada que el usuario no tiene control directo de. Hasta que esté listo, devuelva un estado de "ejecución" al usuario final y, cuando termine, procese los resultados reales.

Cuestiones relacionadas