我正在构建一个需要扩展的java服务器.其中一个servlet将提供存储在Amazon S3中的图像.
最近在加载时,我的虚拟机内存耗尽了,之后我添加了代码来提供图像,所以我很确定流式传输更大的servlet响应会导致我的麻烦.
我的问题是:在从数据库或其他云存储中读取时,如何编写java servlet以将大型(> 200k)响应流回浏览器,是否有任何最佳实践?
我已经考虑将文件写入本地临时驱动器,然后生成另一个线程来处理流,以便可以重用tomcat servlet线程.这似乎很重要.
任何想法将不胜感激.谢谢.
如果可能,您不应存储要在内存中提供的文件的全部内容.相反,为数据获取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上的文件.我对该技术的理解是:
初始HTML页面包含指向您的webapp的下载链接
用户点击下载链接
您的webapp会生成一个S3 URL,其中包含一个过期的密钥,比如5分钟.
使用步骤3中的URL将HTTP重定向发送到客户端.
用户从S3下载文件.即使下载时间超过5分钟,这仍然有效 - 一旦下载开始,它可以继续完成.
你为什么不把它们指向S3网址?从S3获取一个工件,然后通过您自己的服务器将其流式传输给我,这就失去了使用S3的目的,即卸载带宽和处理将图像提供给亚马逊的过程.
我已经看到很多代码,比如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条明确定义的模式与适当的流关闭看起来非常稳固.