2009-03-26 17 views
35

Estoy usando IBM Websphere Application Server v6 y Java 1.4 y estoy tratando de escribir archivos CSV grandes en el ServletOutputStream para que los descargue un usuario. Los archivos van desde 50-750MB en este momento.Usando ServletOutputStream para escribir archivos muy grandes en un servlet Java sin problemas de memoria

Los archivos más pequeños no están causando demasiado problema, pero con los archivos más grandes parece que se está escribiendo en el montón que está causando un error OutOfMemory y derribando todo el servidor.

Estos archivos solo se pueden servir a los usuarios autenticados a través de HTTPS y es por eso que los estoy sirviendo a través de un Servlet en lugar de simplemente colocarlos en Apache.

El código que estoy utilizando es (un poco retirado en torno a esta pelusa):

resp.setHeader("Content-length", "" + fileLength); 
    resp.setContentType("application/vnd.ms-excel"); 
    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\""); 

    FileInputStream inputStream = null; 

    try 
    { 
     inputStream = new FileInputStream(path); 
     byte[] buffer = new byte[1024]; 
     int bytesRead = 0; 

     do 
     { 
      bytesRead = inputStream.read(buffer, offset, buffer.length); 
      resp.getOutputStream().write(buffer, 0, bytesRead); 
     } 
     while (bytesRead == buffer.length); 

     resp.getOutputStream().flush(); 
    } 
    finally 
    { 
     if(inputStream != null) 
      inputStream.close(); 
    } 

El FileInputStream no parece ser la causa de un problema, ya que si escribo a otro archivo o simplemente quitar la escritura por completo la el uso de la memoria no parece ser un problema.

Lo que estoy pensando es que el resp.getOutputStream().write se está almacenando en la memoria hasta que los datos puedan enviarse al cliente. ¡De modo que el archivo completo podría leerse y almacenarse en el resp.getOutputStream() causando problemas de memoria y fallando!

He intentado guardar estas secuencias en el búfer y también he intentado usar Canales desde java.nio, ninguno de los cuales parece tener una pequeña diferencia en mis problemas de memoria. También he lavado el OutputStream una vez por iteración del ciclo y después del ciclo, lo que no ayudó.

+2

establecer esta propiedad personalizada de contenedor Web Websphere Try - com.ibm.ws.webcontainer.channelwritetype = sincronización detalles están aquí - http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/ index.jsp? topic =/com.ibm.websphere.express.doc/info/exp/ae/rweb_custom_props.html –

Respuesta

39

El propio servletcontainer decente promedio descarga la corriente por defecto cada ~ 2 KB. Realmente no debería tener la necesidad de llamar explícitamente al flush() en el OutputStream del HttpServletResponse a intervalos cuando se transmiten datos secuencialmente desde la misma fuente. En, por ejemplo, Tomcat (y Websphere!) Se puede configurar como atributo bufferSize del conector HTTP.

El servletcontainer decente medio también solo transmite los datos en chunks si la longitud del contenido es desconocida de antemano (según el Servlet API specification) y si el cliente admite HTTP 1.1.

Los síntomas del problema al menos indican que el servletcontainer almacena en búfer todo el flujo en la memoria antes de enjuagar. Esto puede significar que el encabezado de longitud del contenido no está configurado y/o el servletcontainer no admite la codificación fragmentada y/o el lado del cliente no admite la codificación fragmentada (es decir, está utilizando HTTP 1.0).

para fijar la una o la otra, acaba de establecer la longitud del contenido de antemano:

response.setHeader("Content-Length", String.valueOf(new File(path).length())); 
1

Funciona flush en la secuencia de salida.

Realmente quería comentar que se debe utilizar la forma de tres arg de escritura como el tampón no es necesariamente totalmente leer (sobre todo al final del archivo (!)). También una prueba/finalmente estaría en orden a menos que quieras que tu servidor muera inesperadamente.

+0

La descarga funciona en la salida de salida. Sí, tiene un bloque try/finally alrededor, el inputstream está cerrado en él. Lo he intentado con la versión 1 y 3-arg tanto de lectura como de escritura, y no pareció marcar la diferencia, por lo que, para ser más comprensibles, utilicé la versión de 1 arg en la publicación. – Martin

0

no relacionado con sus problemas de memoria, mientras que el bucle debe ser:

while(bytesRead > 0); 
+0

Hmm si configuro el bucle while para que nunca escriba nada en la secuencia de salida. A menos que mueva una lectura inicial fuera del ciclo.Tal vez debería usar esto en cambio mientras ((bytesRead = inputStream.read (buffer, offset, buffer.length))! = -1) sería más seguro. De cualquier manera no relacionada :( – Martin

+0

advertencia: devolver 0 bytes es perfectamente posible y no debe terminar el ciclo. – eckes

1

He utilizado una clase que envuelve el OutputStream para que sea reutilizable en otros contextos. Me ha funcionado bien para obtener datos para el navegador más rápido, pero no he visto las implicaciones de la memoria. (Por favor, perdona mi anticuada m_ variable de nomenclatura)

import java.io.IOException; 
import java.io.OutputStream; 

public class AutoFlushOutputStream extends OutputStream { 

    protected long m_count = 0; 
    protected long m_limit = 4096; 
    protected OutputStream m_out; 

    public AutoFlushOutputStream(OutputStream out) { 
     m_out = out; 
    } 

    public AutoFlushOutputStream(OutputStream out, long limit) { 
     m_out = out; 
     m_limit = limit; 
    } 

    public void write(int b) throws IOException { 

     if (m_out != null) { 
      m_out.write(b); 
      m_count++; 
      if (m_limit > 0 && m_count >= m_limit) { 
       m_out.flush(); 
       m_count = 0; 
      } 
     } 
    } 
} 
1

Tampoco estoy seguro de si flush() en ServletOutputStream obras en este caso, pero ServletResponse.flushBuffer() deben enviar la respuesta al cliente (al menos por especificación servlet 2.3).

ServletResponse.setBufferSize() suena prometedor, también.

1

Así que, después de su escenario, ¿no deberías sido ras (ing) dentro de ese bucle while (en cada iteración), en lugar de fuera de ella? Yo probaría eso, aunque con un poco más de búfer.

1
  1. clase de Kevin debe cerrar el campo m_out si no es nulo en el operador close(), no lo hacemos quiero filtrar cosas, ¿verdad?

  2. Además del operador ServletOutputStream.flush(), la operación HttpServletResponse.flushBuffer() también puede vaciar los búfers. Sin embargo, parece ser un detalle específico de la implementación en cuanto a si estas operaciones tienen o no efecto, o si el soporte de longitud de contenido http está interfiriendo. Recuerde, especificar la longitud del contenido es una opción en HTTP 1.0, por lo que las cosas deberían fluir si descarga cosas. Pero no veo que

+0

1) eso es discutible. la clase no creó la secuencia, por lo que podría argumentar que no tiene propiedad y no debe realizar operaciones cercanas. – Renan

1

La condición while no funciona, debe verificar el -1 antes de usarlo. Y utilice una variable temporal para la secuencia de salida, es más fácil de leer y hace una llamada segura al getOutputStream() repetidamente.

OutputStream outStream = resp.getOutputStream(); 
while(true) { 
    int bytesRead = inputStream.read(buffer); 
    if (bytesRead < 0) 
     break; 
    outStream.write(buffer, 0, bytesRead); 
} 
inputStream.close(); 
out.close(); 
0

su código tiene un bucle infinito.

do 
{ 
    bytesRead = inputStream.read(buffer, offset, buffer.length); 
    resp.getOutputStream().write(buffer, 0, bytesRead); 
} 
while (bytesRead == buffer.length); 

compensar tiene el mismo valor thoughout del bucle, por lo que si inicialmente offset = 0, permanecerá por lo que en cada iteración que hará que infinito-bucle y que se conduce a error OOM.

-1

El servidor de aplicaciones Ibm websphere utiliza la transferencia asíncrona de datos para los servlets de forma predeterminada. Eso significa que amortigua la respuesta. Si tiene problemas con grandes cantidades de datos y excepciones de OutOfMemory, intente cambiar las configuraciones en WAS para usar el modo síncrono.

Setting the WebSphere Application Server WebContainer to synchronous mode

También debe tener cuidado de trozos de carga y lávelos. Muestra para cargar desde un archivo grande.

ServletOutputStream os = response.getOutputStream(); 
FileInputStream fis = new FileInputStream(file); 
      try { 
       int buffSize = 1024; 
       byte[] buffer = new byte[buffSize]; 
       int len; 
       while ((len = fis.read(buffer)) != -1) { 
        os.write(buffer, 0, len); 
        os.flush(); 
        response.flushBuffer(); 
       } 
      } finally { 
       os.close(); 
      } 
Cuestiones relacionadas