流式传输java servlet中的大型文件

时间:2008-09-11 02:40:13

标签: java java-io

我正在构建一个需要扩展的java服务器。其中一个servlet将提供存储在Amazon S3中的图像。

最近在加载时,我的虚拟机内存耗尽了,之后我添加了代码来提供图像,所以我很确定流式传输更大的servlet响应会导致我的麻烦。

我的问题是:在从数据库或其他云存储中读取时,如何编写java servlet以将大型(> 200k)响应流回浏览器是否有最佳实践?

我考虑将文件写入本地临时驱动器,然后生成另一个线程来处理流,以便可以重用tomcat servlet线程。这似乎很重要。

任何想法都将不胜感激。感谢。

8 个答案:

答案 0 :(得分:52)

如果可能,您不应存储要在内存中提供的文件的全部内容。相反,为数据获取InputStream,并将数据复制到Servlet OutputStream中。例如:

ServletOutputStream out = response.getOutputStream();
InputStream in = [ code to get source input stream ];
String mimeType = [ code to get mimetype of data to be served ];
byte[] bytes = new byte[FILEBUFFERSIZE];
int bytesRead;

response.setContentType(mimeType);

while ((bytesRead = in.read(bytes)) != -1) {
    out.write(bytes, 0, bytesRead);
}

// do the following in a finally block:
in.close();
out.close();

我同意toby,你应该“将它们指向S3网址。”

至于OOM例外,你确定它与提供图像数据有关吗?假设你的JVM有256MB的“额外”内存用于提供图像数据。在Google的帮助下,“256MB / 200KB”= 1310.对于2GB“额外”内存(这些天非常合理的数量),可以支持超过10,000个并发客户端。即便如此,1300个同步客户端数量也相当大。这是您遇到的负载类型吗?如果没有,您可能需要在其他地方寻找OOM异常的原因。

编辑 - 关于:

  

在此用例中,图像可能包含敏感数据......

几周前,当我阅读S3文档时,我注意到您可以生成可以附加到S3 URL的时间过期密钥。因此,您不必向公众开放S3上的文件。我对这项技术的理解是:

  1. 初始HTML页面包含指向您的webapp的下载链接
  2. 用户点击下载链接
  3. 您的网络应用会生成一个S3网址,其中包含一个过期的密钥,比方说,5分钟。
  4. 使用步骤3中的URL向客户端发送HTTP重定向。
  5. 用户从S3下载文件。即使下载时间超过5分钟,这仍然有效 - 一旦下载开始,它就可以继续完成。

答案 1 :(得分:17)

为什么不将它们指向S3网址?从S3获取一个工件,然后通过你自己的服务器将它流式传输给我,这就失去了使用S3的目的,即卸载带宽和处理将图像提供给亚马逊。

答案 2 :(得分:10)

我已经看过很多像john-vasilef(目前接受的)回答的代码,一个紧密的while循环从一个流中读取块并将它们写入另一个流。

我所提出的论点是反对不必要的代码重复,支持使用Apache的IOUtils。如果您已经在其他地方使用它,或者您正在使用的其他库或框架已经依赖它,那么它就是一条已知且经过充分测试的单行。

在以下代码中,我将对象从Amazon S3流式传输到servlet中的客户端。

import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;

InputStream in = null;
OutputStream out = null;

try {
    in = object.getObjectContent();
    out = response.getOutputStream();
    IOUtils.copy(in, out);
} finally {
    IOUtils.closeQuietly(in);
    IOUtils.closeQuietly(out);
}

6行明确定义的模式以及正确的流关闭似乎非常可靠。

答案 3 :(得分:2)

我非常赞同toby和John Vasileff - 如果您能够容忍相关问题,S3非常适合卸载大型媒体对象。 (自己应用程序的一个实例为10-1000MB FLV和MP4执行此操作。)例如:没有部分请求(字节范围标题)。一个人必须处理“手动”,偶尔停工等等。

如果那不是一个选项,John的代码看起来不错。我发现2k FILEBUFFERSIZE的字节缓冲区在微基准标记中效率最高。另一个选项可能是共享的FileChannel。 (FileChannels是线程安全的。)

那就是说,我还要补充一点,猜测导致内存不足错误的原因是一个典型的优化错误。通过使用硬指标,您可以提高成功的机会。

  1. 将-XX:+ HeapDumpOnOutOfMemoryError放入JVM启动参数中,以防万一
  2. 在正在运行的JVM( jmap -histo< pid> )下使用jmap加载
  3. 分析指标(jmap -histo out put,或者让jhat查看堆转储)。很可能你的记忆力来自某个意想不到的地方。
  4. 当然还有其他工具,但jmap& jhat附带Java 5+'开箱即用'

      

    我考虑将文件写入本地临时驱动器,然后生成另一个线程来处理流,以便可以重用tomcat servlet线程。这似乎很重要。

    啊,我认为你不能那样做。即使你可以,它听起来很可疑。管理连接的tomcat线程需要控制。如果遇到线程饥饿,则增加./conf/server.xml中的可用线程数。同样,指标是检测这种情况的方法 - 不要猜测。

    问题:您是否也在使用EC2?你的tomcat的JVM启动参数是什么?

答案 4 :(得分:1)

toby是对的,你应该直接指向S3,如果可以的话。如果你不能,那么提出准确回答的问题有点模糊: 你的java堆有多大?当内存不足时,会同时打开多少个流? 你的读写/缓冲有多大(8K好)? 你正在从流中读取8K,然后将8k写入输出,对吗?您不是试图从S3读取整个图像,将其缓冲在内存中,然后立即发送整个图像?

如果你使用8K缓冲区,你可能有1000个并发流进入~8Megs的堆空间,所以你肯定做错了......

顺便说一句,我没有凭空挑选8K,它是套接字缓冲区的默认大小,发送更多数据,比如1Meg,你将在tcp / ip堆栈上阻塞大量内存。 / p>

答案 5 :(得分:0)

你必须检查两件事:

  • 你是在关闭流吗?非常重要
  • 也许你正在免费提供流连接。流不是很大,但同时许多流可以窃取你的所有内存。创建一个池,以便您不能同时运行一定数量的流

答案 6 :(得分:0)

除了John建议的内容之外,您还应该反复刷新输出流。根据您的Web容器,它可能会缓存部分甚至所有输出并立即刷新它(例如,计算Content-Length标头)。那会烧掉相当多的记忆。

答案 7 :(得分:0)

如果您可以构建文件以便静态文件是独立的并且在自己的存储桶中,那么今天可以通过使用Amazon S3 CDN CloudFront来实现最快的性能。