我正在构建一个需要扩展的java服务器。其中一个servlet将提供存储在Amazon S3中的图像。
最近在加载时,我的虚拟机内存耗尽了,之后我添加了代码来提供图像,所以我很确定流式传输更大的servlet响应会导致我的麻烦。
我的问题是:在从数据库或其他云存储中读取时,如何编写java servlet以将大型(> 200k)响应流回浏览器是否有最佳实践?
我考虑将文件写入本地临时驱动器,然后生成另一个线程来处理流,以便可以重用tomcat servlet线程。这似乎很重要。
任何想法都将不胜感激。感谢。
答案 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 :(得分: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是线程安全的。)
那就是说,我还要补充一点,猜测导致内存不足错误的原因是一个典型的优化错误。通过使用硬指标,您可以提高成功的机会。
当然还有其他工具,但jmap& jhat附带Java 5+'开箱即用'
啊,我认为你不能那样做。即使你可以,它听起来很可疑。管理连接的tomcat线程需要控制。如果遇到线程饥饿,则增加./conf/server.xml中的可用线程数。同样,指标是检测这种情况的方法 - 不要猜测。我考虑将文件写入本地临时驱动器,然后生成另一个线程来处理流,以便可以重用tomcat servlet线程。这似乎很重要。
问题:您是否也在使用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来实现最快的性能。