使用jetty做为server提供多线程文件下载
背景
??最近在做的一个项目,两个java进程之间会涉及一个大数据量的传递过程,基本都是图片文件,(做了压缩后还是会比较大,最大的有超过600MB)。其次这两个java进程是在跨机房,比如中国和美国机房,网络待框也就几百kB。
?
??这就是本文的项目背景
分析1. ?600MB的文件,都是A进程运行时根据需要生成的(下载需要的图片文件)。所以无法预先处理,而且公司总图片文件都是以TB计算,所以全量同步的方案也不靠谱
2. ?从A进程到B进程的数据传递,首先想到用socket进行传递,但单socket的数据同步无法满足需求,多线程数据传递会涉及数据的切片和数据的合并等,代码相对会比较复杂
?
老的项目实现:
A先临时保存文件到一指定目录A进程机器上启动一个http服务(比如nginx,lighttpd)B进程外部调用一个多线程下载客户端,下载数据到一临时目录B进程等下载完成后,再操纵临时目录的数据项目的工程代码都是以java,引入了多线程服务和下载客户端之后,增加了项目的部署和维护成本。所以在项目重构时,想的一个办法是用嵌入式的jetty,去替换nginx提供http服务。过程涉及到多线程下载和断点续载,首先得了解一个Http协议的内容。
?
Byte Ranges: 文档http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
?
大致的内容,可以在Http的Header头中进行添加,可以指定文件下载byte的start和end的位置,几个例子:
bytes=100-499 bytes=-300bytes=100-bytes=1-2,2-3,6-,-2
?
最后,通过tcpdump进行数据抓包分析,多线程下载客户端的Http协议的内容。基本的思路是根据线程数,计算出每个线程的bytes-range,然后每个线程发起一个独立的请求。后端每个处理线程单独处理bytes-range的数据下载。
至于断点续传,其实也相对比较简单了。就是记录好分出去的每个bytes-range成功与否,失败的重新再做,已经做完的可以直接跳过。
?
抓取的Http协议内容:
GET /source.tar.gz HTTP/1.1User-Agent: aria2/1.13.0Accept: */*Host: 10.20.156.49:8080Pragma: no-cacheCache-Control: no-cacheRange: bytes=258998272-387973119java版的多线程下载支持
一提到做java版的多线程下载,首先就会想到jetty和tomcat。tomcat只能以外部server的方式进行启动,和nginx没有太多的区别。
最后我选择了jetty,并在项目中做为嵌入式进行启动,提供http多线程下载服务。
?
按照前面的分析,要做多线程下载,无非就是要实现一个bytes-range的处理。还好我用的jetty版本(7.0.1)已经解析了bytes-range,具体解析类:InclusiveByteRange
?
再仔细翻了下它的代码,发现jetty已经默认提供了一个servlet支持多线程下载,就是DefaultServlet,甚喜。
?
最后,按照我项目的需求适当的裁剪了一些代码,最后完成了:DownloadServlet,具体代码见附件。
?
类中使用了java版的sendfile,推荐看一下:http://stackoverflow.com/questions/1605332/java-nio-filechannel-versus-fileoutputstream-performance-usefulness
?

1. 引入相关的jar
?
<dependency> <groupId>com.alibaba.external</groupId> <artifactId>server.jetty.jetty-servlet</artifactId> <version>${jetty_verion}</version></dependency><dependency> <groupId>com.alibaba.external</groupId> <artifactId>server.jetty.jetty-xml</artifactId> <version>${jetty_verion}</version></dependency><dependency> <groupId>com.alibaba.external</groupId> <artifactId>server.jetty.jetty-server</artifactId> <version>${jetty_verion}</version></dependency>?
2. ?配置jetty.xml (我选择了xml的配置方式,但没有使用war包,我只需要一个Http服务功能即可)
<Configure id="Server" default="8080"/></Set> <Set name="forwarded">true</Set> <Set name="forwardedHostHeader">ignore</Set> <Set name="forwardedServerHeader">ignore</Set> <Set name="acceptQueueSize">256</Set> <Set name="statsOn">false</Set> <Set name="maxIdleTime">600000</Set> <Set name="lowResourcesMaxIdleTime">5000</Set> <Set name="requestHeaderSize">8192</Set> <Set name="responseHeaderSize">8192</Set> </New> </Arg> </Call> <!-- <Call name="addConnector"> <Arg> <New /></Set> <Set name="port"><Property name="jetty.port" default="8080"/></Set> <Set name="forwarded">true</Set> <Set name="forwardedHostHeader">ignore</Set> <Set name="forwardedServerHeader">ignore</Set> <Set name="maxIdleTime">600000</Set> <Set name="Acceptors">2</Set> <Set name="acceptQueueSize">256</Set> <Set name="statsOn">false</Set> <Set name="confidentialPort">8443</Set> <Set name="lowResourcesConnections">2000</Set> <Set name="lowResourcesMaxIdleTime">5000</Set> <Set name="requestHeaderSize">8192</Set><Set name="responseHeaderSize">8192</Set> </New> </Arg> </Call> --> <!-- =========================================================== --> <!-- Set handler Collection Structure --> <!-- =========================================================== --> <Set name="handler"> <New id="Handlers" name="code">Resource jetty_xml = Resource.newSystemResource("jetty/jetty.xml");XmlConfiguration configuration = new XmlConfiguration(jetty_xml.getInputStream());Server server = (Server) configuration.configure();server.start();?
测试
?
??最后选择了几个多线程下载的客户端进行了测试,我这里选择了aria2c(http://aria2.sourceforge.net/) ?和 axel(http://www.axel.com/uk2/)
?
aria2c测试
参数:
--no-conf -x 10 -s 10 -j 10 --timeout=600 --max-tries=5 --stop=1800 --allow-overwrite=true --enable-http-keep-alive=true --log-level=warn
下载1.1GB的文件:
?? apache : 28s
?? nginx ?: 27s
?? jetty ? : 27s
axel测试参数:
-n 10 -a -v ?
下载1.1GB的文件:?
?? apache : 87s
?? nginx ?: 87s
?? jetty : 88s
?
总结并没有做非常详尽的性能测试,不过从几次跑的结果来看,基本上也有数了。
- jetty实现的servlet性能基本和nginx,apache下载接近。而且测试过程中瓶颈已经不在应用本身,基本都在网络带宽上了,我是百MB网卡,基本可以满负荷运转。jetty的nio和bio版本,nio在context switch切换上会相对比较多(因为有大量的READ/WRITE事件响应,线程切换反而不如bio来得少),建议部署bio模式多线程下载aria2c工具的确不错,推荐使用
操作系统层面会对文件流有cache,上一次下载后会有cache,会音像测试结果。 2 楼 elvishehai 2012-03-30 这个能够同时起两个server吗 3 楼 agapple 2012-03-30 elvishehai 写道这个能够同时起两个server吗
端口不同,当然可以