zoukankan      html  css  js  c++  java
  • 使用jetty做为server提供多线程文件下载

    http://agapple.iteye.com/blog/1189612

    ————————————————————————————————————————————————————————

    使用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的位置,几个例子:

    Java代码  收藏代码
    1. bytes=100-499    
    2. bytes=-300  
    3. bytes=100-  
    4. bytes=1-2,2-3,6-,-2  

    最后,通过tcpdump进行数据抓包分析,多线程下载客户端的Http协议的内容。基本的思路是根据线程数,计算出每个线程的bytes-range,然后每个线程发起一个独立的请求。后端每个处理线程单独处理bytes-range的数据下载。

    至于断点续传,其实也相对比较简单了。就是记录好分出去的每个bytes-range成功与否,失败的重新再做,已经做完的可以直接跳过。

    抓取的Http协议内容:

    Java代码  收藏代码
    1. GET /source.tar.gz HTTP/1.1  
    2. User-Agent: aria2/1.13.0  
    3. Accept: */*  
    4. Host: 10.20.156.49:8080  
    5. Pragma: no-cache  
    6. Cache-Control: no-cache  
    7. 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


    将jetty引入做为嵌入式启动的步骤

    1. 引入相关的jar

    Xml代码  收藏代码
    1. <dependency>  
    2.   <groupId>com.alibaba.external</groupId>  
    3.   <artifactId>server.jetty.jetty-servlet</artifactId>  
    4.   <version>${jetty_verion}</version>  
    5. </dependency>  
    6. <dependency>  
    7.   <groupId>com.alibaba.external</groupId>  
    8.   <artifactId>server.jetty.jetty-xml</artifactId>  
    9.   <version>${jetty_verion}</version>  
    10. </dependency>  
    11. <dependency>  
    12.   <groupId>com.alibaba.external</groupId>  
    13.   <artifactId>server.jetty.jetty-server</artifactId>  
    14.   <version>${jetty_verion}</version>  
    15. </dependency>  

    2.  配置jetty.xml (我选择了xml的配置方式,但没有使用war包,我只需要一个Http服务功能即可)

    Java代码  收藏代码
    1. <Configure id="Server" class="org.eclipse.jetty.server.Server">  
    2.   
    3.     <!-- =========================================================== -->  
    4.     <!-- Server Thread Pool                                          -->  
    5.     <!-- =========================================================== -->  
    6.     <Set name="ThreadPool">  
    7.       <!-- Default queued blocking threadpool -->  
    8.       <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">  
    9.         <Set name="minThreads">10</Set>  
    10.         <Set name="maxThreads">250</Set>  
    11.       </New>  
    12.     </Set>  
    13.   
    14.     <!-- =========================================================== -->  
    15.     <!-- Set connectors                                              -->  
    16.     <!-- =========================================================== -->  
    17.     <!-- -->  
    18.     <Call name="addConnector">  
    19.       <Arg>  
    20.           <New class="org.eclipse.jetty.server.bio.SocketConnector">  
    21.             <Set name="port"><Property name="jetty.bio.port" default="8080"/></Set>  
    22.             <Set name="forwarded">true</Set>  
    23.             <Set name="forwardedHostHeader">ignore</Set>  
    24.             <Set name="forwardedServerHeader">ignore</Set>  
    25.             <Set name="acceptQueueSize">256</Set>  
    26.             <Set name="statsOn">false</Set>  
    27.             <Set name="maxIdleTime">600000</Set>  
    28.             <Set name="lowResourcesMaxIdleTime">5000</Set>  
    29.             <Set name="requestHeaderSize">8192</Set>  
    30.         <Set name="responseHeaderSize">8192</Set>  
    31.           </New>  
    32.       </Arg>  
    33.     </Call>  
    34.     <!--   
    35.     <Call name="addConnector">  
    36.       <Arg>  
    37.           <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">  
    38.             <Set name="host"><Property name="jetty.host" /></Set>  
    39.             <Set name="port"><Property name="jetty.port" default="8080"/></Set>  
    40.             <Set name="forwarded">true</Set>  
    41.             <Set name="forwardedHostHeader">ignore</Set>  
    42.             <Set name="forwardedServerHeader">ignore</Set>  
    43.             <Set name="maxIdleTime">600000</Set>  
    44.             <Set name="Acceptors">2</Set>  
    45.             <Set name="acceptQueueSize">256</Set>  
    46.             <Set name="statsOn">false</Set>  
    47.             <Set name="confidentialPort">8443</Set>  
    48.             <Set name="lowResourcesConnections">2000</Set>  
    49.             <Set name="lowResourcesMaxIdleTime">5000</Set>  
    50.             <Set name="requestHeaderSize">8192</Set>  
    51.             <Set name="responseHeaderSize">8192</Set>  
    52.           </New>  
    53.       </Arg>  
    54.     </Call>  
    55.      -->  
    56.     <!-- =========================================================== -->  
    57.     <!-- Set handler Collection Structure                            -->  
    58.     <!-- =========================================================== -->  
    59.     <Set name="handler">  
    60.       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">  
    61.         <Set name="handlers">  
    62.          <Array type="org.eclipse.jetty.server.Handler">  
    63.            <Item>  
    64.              <New id="ServletHandler" class="org.eclipse.jetty.servlet.ServletContextHandler">  
    65.                 <Set name="contextPath">/</Set>  
    66.                 <Call name="addServlet">  
    67.                     <Arg>com.alibaba.otter.task.biz.common.jetty.DownloadServlet</Arg>  
    68.                     <Arg>/*</Arg>  
    69.                 </Call>  
    70.                 <Get name="initParams">  
    71.                     <Put name="org.eclipse.jetty.servlet.Default.resourceBase">/tmp/</Put>  
    72.                     <Put name="org.eclipse.jetty.servlet.Default.gzip">false</Put>  
    73.                 </Get>  
    74.              </New>  
    75.            </Item>  
    76.          </Array>  
    77.         </Set>  
    78.       </New>  
    79.     </Set>  
    80.   
    81.     <!-- =========================================================== -->  
    82.     <!-- extra options                                               -->  
    83.     <!-- =========================================================== -->  
    84.     <Set name="stopAtShutdown">true</Set>  
    85.     <Set name="sendServerVersion">false</Set>  
    86.     <Set name="sendDateHeader">true</Set>  
    87.     <Set name="gracefulShutdown">1000</Set>  
    88. </Configure>  

    说明: 主要的配置见handler,配置了对应的DownloadServlet

    3. 启动入口 (使用了xml配置后就灰常的简洁了)

    Java代码  收藏代码
    1. Resource jetty_xml = Resource.newSystemResource("jetty/jetty.xml");  
    2. XmlConfiguration configuration = new XmlConfiguration(jetty_xml.getInputStream());  
    3. Server server = (Server) configuration.configure();  
    4. server.start();  

    测试

      最后选择了几个多线程下载的客户端进行了测试,我这里选择了aria2c(http://aria2.sourceforge.net/)  和 axel(http://www.axel.com/uk2/)

    aria2c测试

    参数:

    Java代码  收藏代码
    1. --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测试
    参数:
    Java代码  收藏代码
    1. -n 10 -a -v <span style="white-space: normal;"> </span>  

    下载1.1GB的文件: 

       apache : 87s
       nginx  : 87s

       jetty : 88s

    总结

    并没有做非常详尽的性能测试,不过从几次跑的结果来看,基本上也有数了。

    1. jetty实现的servlet性能基本和nginx,apache下载接近。而且测试过程中瓶颈已经不在应用本身,基本都在网络带宽上了,我是百MB网卡,基本可以满负荷运转。
    2. jetty的nio和bio版本,nio在context switch切换上会相对比较多(因为有大量的READ/WRITE事件响应,线程切换反而不如bio来得少),建议部署bio模式
    3. 多线程下载aria2c工具的确不错,推荐使用
    后续,会尝试使用java写一个多线程的客户端,如果性能还ok的话,可以直接替换aria2c,到时候就是一些jar包,没有了外部软件的依赖,部署和维护也会相对比较简单。
     

    ——————————————————————————————————
    傲轩游戏网
  • 相关阅读:
    类目(分类)
    协议(Protocol)---实例
    OC 复合 组装电脑
    iOS--九宫格奥秘(UIView)(arc4random)
    字符串
    oc 字符串
    七星彩问题
    OC--第一个程序
    关于行内元素垂直居中的一个小小trick
    关于orgChart
  • 原文地址:https://www.cnblogs.com/cuizhf/p/2251240.html
Copyright © 2011-2022 走看看