设为首页 收藏本站
查看: 1083|回复: 0

[经验分享] 使用jetty做为server提供多线程文件下载

[复制链接]

尚未签到

发表于 2017-2-27 08:24:29 | 显示全部楼层 |阅读模式
背景
  最近在做的一个项目,两个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=-300
bytes=100-
bytes=1-2,2-3,6-,-2
  最后,通过tcpdump进行数据抓包分析,多线程下载客户端的Http协议的内容。基本的思路是根据线程数,计算出每个线程的bytes-range,然后每个线程发起一个独立的请求。后端每个处理线程单独处理bytes-range的数据下载。
  至于断点续传,其实也相对比较简单了。就是记录好分出去的每个bytes-range成功与否,失败的重新再做,已经做完的可以直接跳过。
  抓取的Http协议内容:

GET /source.tar.gz HTTP/1.1
User-Agent: aria2/1.13.0
Accept: */*
Host: 10.20.156.49:8080
Pragma: no-cache
Cache-Control: no-cache
Range: bytes=258998272-387973119
java版的多线程下载支持
  一提到做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
  
DSC0000.png

将jetty引入做为嵌入式启动的步骤
  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" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- Server Thread Pool                                          -->
<!-- =========================================================== -->
<Set name="ThreadPool">
<!-- Default queued blocking threadpool -->
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Set name="minThreads">10</Set>
<Set name="maxThreads">250</Set>
</New>
</Set>
<!-- =========================================================== -->
<!-- Set connectors                                              -->
<!-- =========================================================== -->
<!-- -->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.bio.SocketConnector">
<Set name="port"><Property name="jetty.bio.port" 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 class="org.eclipse.jetty.server.nio.SelectChannelConnector">
<Set name="host"><Property name="jetty.host" /></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" class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="ServletHandler" class="org.eclipse.jetty.servlet.ServletContextHandler">
<Set name="contextPath">/</Set>
<Call name="addServlet">
<Arg>com.alibaba.otter.task.biz.common.jetty.DownloadServlet</Arg>
<Arg>/*</Arg>
</Call>
<Get name="initParams">
<Put name="org.eclipse.jetty.servlet.Default.resourceBase">/tmp/</Put>
<Put name="org.eclipse.jetty.servlet.Default.gzip">false</Put>
</Get>
</New>
</Item>
</Array>
</Set>
</New>
</Set>
<!-- =========================================================== -->
<!-- extra options                                               -->
<!-- =========================================================== -->
<Set name="stopAtShutdown">true</Set>
<Set name="sendServerVersion">false</Set>
<Set name="sendDateHeader">true</Set>
<Set name="gracefulShutdown">1000</Set>
</Configure>
  说明: 主要的配置见handler,配置了对应的DownloadServlet
  3. 启动入口 (使用了xml配置后就灰常的简洁了)

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工具的确不错,推荐使用

后续,会尝试使用java写一个多线程的客户端,如果性能还ok的话,可以直接替换aria2c,到时候就是一些jar包,没有了外部软件的依赖,部署和维护也会相对比较简单。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-347641-1-1.html 上篇帖子: jetty: 请求的操作无法在使用用户映射区域打开的文件上执行 下篇帖子: 解决JettyWTPPlugin启动Jetty时报错:NoClassDefFoundError: org/objectweb/asm/ClassVisitor
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表