2009-03-26 36 views
35

Tôi đang sử dụng Máy chủ ứng dụng Websphere của IBM v6 và Java 1.4 và đang cố ghi các tệp CSV lớn vào ServletOutputStream để người dùng tải xuống. Các tệp khác nhau từ 50-750MB vào lúc này.Sử dụng ServletOutputStream để viết các tệp rất lớn trong một servlet Java không có vấn đề về bộ nhớ

Các tệp nhỏ hơn không gây ra quá nhiều vấn đề nhưng với các tệp lớn hơn, có vẻ như tệp đang được ghi vào heap, sau đó gây ra lỗi OutOfMemory và đưa toàn bộ máy chủ xuống.

Những tệp này chỉ có thể được phân phát cho người dùng được xác thực qua HTTPS, đó là lý do tại sao tôi phục vụ chúng thông qua Servlet thay vì chỉ gắn chúng vào Apache.

Mã Tôi đang sử dụng là (một số khuẩn loại bỏ khoảng này):

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(); 
    } 

Các FileInputStream dường như không thể gây ra một vấn đề là nếu tôi viết vào tập tin khác hoặc chỉ loại bỏ các ghi hoàn toàn việc sử dụng bộ nhớ dường như không phải là vấn đề.

Điều tôi đang nghĩ là resp.getOutputStream().write đang được lưu trữ trong bộ nhớ cho đến khi dữ liệu có thể được gửi qua máy khách. Vì vậy, toàn bộ tập tin có thể được đọc và lưu trữ trong các resp.getOutputStream() gây ra vấn đề bộ nhớ của tôi và đâm!

Tôi đã thử Buffering các luồng này và cũng đã thử sử dụng Kênh từ java.nio, không cách nào trong số đó dường như tạo ra bất kỳ sự khác biệt nào về vấn đề bộ nhớ của tôi. Tôi cũng đã xóa sạch OutputStream một lần cho mỗi lần lặp của vòng lặp và sau vòng lặp, điều này không giúp ích gì.

+2

Cố gắng thiết lập thuộc tính tùy chỉnh chứa Web Websphere này - com.ibm.ws.webcontainer.channelwritetype = sync chi tiết đang ở đây - http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/ index.jsp? topic =/com.ibm.websphere.express.doc/info/exp/ae/rweb_custom_props.html –

Trả lời

39

Các servletcontainer trung bình khá tự xóa luồng theo mặc định mỗi ~ 2KB. Bạn thực sự không cần phải gọi rõ ràng flush() trên OutputStream của HttpServletResponse trong các khoảng thời gian khi truyền dữ liệu tuần tự từ cùng một nguồn. Trong ví dụ Tomcat (và Websphere!), Cấu hình này có thể được cấu hình là thuộc tính bufferSize của trình kết nối HTTP.

Trình dịch vụ servlet phù hợp trung bình cũng chỉ truyền dữ liệu trong chunks nếu độ dài nội dung không xác định trước (theo số Servlet API specification!) Và nếu máy khách hỗ trợ HTTP 1.1.

Các triệu chứng sự cố ít nhất chỉ ra rằng servletcontainer đang đệm toàn bộ luồng trong bộ nhớ trước khi xả. Điều này có nghĩa là tiêu đề chiều dài nội dung không được đặt và/hoặc servletcontainer không hỗ trợ mã hóa chunked và/hoặc phía máy khách không hỗ trợ mã hóa chunked (tức là nó đang sử dụng HTTP 1.0).

Để khắc phục một hoặc khác, chỉ cần thiết lập chiều dài nội dung trước:

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

flush hoạt động trên luồng đầu ra không.

Thực sự tôi muốn nhận xét rằng bạn nên sử dụng hình thức ba arg viết vì bộ đệm không nhất thiết phải đọc đầy đủ (đặc biệt là ở phần cuối của tệp (!)). Ngoài ra một thử/cuối cùng sẽ được theo thứ tự trừ khi bạn muốn bạn máy chủ chết bất ngờ.

+0

Tuôn ra hoạt động trên đầu ra. Vâng, nó có một khối thử/cuối cùng xung quanh nó, luồng đầu vào được đóng trong đó. Tôi đã thử với cả 1 và 3-arg phiên bản của cả đọc và viết và nó dường như không tạo ra một sự khác biệt vì vậy cho khả năng đọc vì tôi đã sử dụng phiên bản 1 arg trong bài viết. – Martin

0

không liên quan đến vấn đề trí nhớ của bạn, vòng lặp while nên là:

while(bytesRead > 0); 
+0

Hmm nếu tôi đặt vòng lặp while đến mức nó sẽ không bao giờ ghi bất kỳ thứ gì vào luồng đầu ra. Trừ khi tôi di chuyển một đọc ban đầu bên ngoài vòng lặp.Có lẽ tôi nên sử dụng điều này thay vì trong khi ((bytesRead = inputStream.read (buffer, offset, buffer.length))! = -1) sẽ an toàn hơn. Dù bằng cách nào không liên quan: ( – Martin

+0

cảnh báo: trả lại 0 byte là hoàn toàn có thể và không nên chấm dứt vòng lặp. – eckes

1

Tôi đã sử dụng một lớp học mà kết thúc tốt đẹp OutputStream để làm cho nó tái sử dụng trong những bối cảnh khác. Nó đã làm việc tốt cho tôi trong việc đưa dữ liệu vào trình duyệt nhanh hơn, nhưng tôi đã không xem xét các tác động của bộ nhớ. (Xin vui lòng tha thứ m_ cổ biến đặt tên của tôi)

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

Tôi cũng không chắc chắn nếu flush() trên ServletOutputStream công trình trong trường hợp này, nhưng ServletResponse.flushBuffer() nên gửi câu trả lời cho khách hàng (ít nhất mỗi 2.3 servlet spec).

ServletResponse.setBufferSize() âm thanh đầy hứa hẹn.

1

Vì vậy, theo kịch bản của bạn, bạn không nên tuôn ra (ing) bên trong vòng lặp đó (trên mỗi lần lặp), thay vì ở bên ngoài nó? Tôi sẽ thử rằng, với một bộ đệm lớn hơn một chút mặc dù.

1
  1. lớp của Kevin nên đóng lĩnh vực m_out nếu nó không phải null trong các nhà điều hành close(), chúng tôi không muốn rò rỉ mọi thứ, phải không?

  2. Cũng như toán tử ServletOutputStream.flush(), hoạt động HttpServletResponse.flushBuffer() cũng có thể xóa bộ đệm. Tuy nhiên, nó dường như là một chi tiết cụ thể về việc thực hiện các hoạt động này có hiệu lực hay không, hoặc hỗ trợ độ dài nội dung http có gây cản trở hay không. Hãy nhớ rằng, chỉ định chiều dài nội dung là một tùy chọn trên HTTP 1.0, vì vậy mọi thứ sẽ chỉ phát trực tuyến nếu bạn tuôn ra mọi thứ. Nhưng tôi không thấy rằng

+0

1) đó là điều gây tranh cãi. lớp học không tạo luồng, vì vậy bạn có thể tranh luận rằng nó không có quyền sở hữu và không nên thực hiện các hoạt động gần. – Renan

1

Điều kiện trong khi không hoạt động, bạn cần kiểm tra -1 trước khi sử dụng. Và hãy sử dụng một biến tạm thời cho luồng đầu ra, nó đẹp hơn để đọc và nó bảo vệ gọi getOutputStream() một cách lặp lại.

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

mã của bạn có vòng lặp vô hạn.

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

bù đắp có giá trị như nhau thoughout vòng lặp, vì vậy nếu ban đầu offset = 0, nó sẽ vẫn như vậy trong mỗi lần lặp đó sẽ gây ra vô hạn vòng lặp và đó sẽ dẫn đến lỗi oom.

-1

Máy chủ ứng dụng websphere của IBM sử dụng truyền dữ liệu không đồng bộ cho các servlet theo mặc định. Điều đó có nghĩa là nó đệm đáp ứng. Nếu bạn gặp sự cố với dữ liệu lớn và ngoại lệ OutOfMemory, hãy thử thay đổi cài đặt trên WAS để sử dụng chế độ đồng bộ.

Setting the WebSphere Application Server WebContainer to synchronous mode

Bạn cũng phải chăm sóc tải khối và xả chúng. Mẫu để tải từ tệp lớn.

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(); 
      } 
Các vấn đề liên quan