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

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协议内容:

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

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服务功能即可)

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配置后就灰常的简洁了)

 

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测试

参数:

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测试

参数:

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包,没有了外部软件的依赖,部署和维护也会相对比较简单。

时间: 2024-09-09 07:09:03

使用jetty做为server提供多线程文件下载的相关文章

十五天精通WCF——第三天 client如何知道server提供的功能清单

原文:十五天精通WCF--第三天 client如何知道server提供的功能清单  通常我们去大保健的时候,都会找姑娘问一下这里能提供什么服务,什么价格,这时候可能姑娘会跟你口述一些服务或者提供一份服务清单,这样的话大 家就可以做到童嫂无欺,这样一份活生生的例子,在wcf中同样是一个道理,只有client了解service能提供哪些功能,client才可以根据server提供的功能进行 消费,那问题来了,service怎么把功能提供给client进行选择呢???这个就是我这一篇要聊的wsdl(w

配置使用 SQL Server提供程序 。

 配置使用 SQL Server提供程序  <configuration>   <system.web> <-- 更改提供程序配置: -->    <membership defaultProvider="AspNetSqlProvider" />   </system.web>   <membership> <--配置提供程序 -->  <providers>     <remove

mongodb-MongoDB 3.0 Java Driver在Linux Server下多线程插入数据异常

问题描述 MongoDB 3.0 Java Driver在Linux Server下多线程插入数据异常 使用MongoDB 3.0版本,部署环境为vSphere构建虚拟机,使用Java driver(3.0.0rc1)多线程写入数据到MongoDB,异常信息如下所示: Caused by: java.lang.NullPointerException at com.mongodb.connection.ProtocolHelper.isCommandOk(ProtocolHelper.java:

Android实现网络多线程文件下载_Android

实现原理 (1)首先获得下载文件的长度,然后设置本地文件的长度. (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置. 如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示: (网上找的图) 例如10M大小,使用3个线程来下载, 线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M 下载开始位置:线程id*每条线程下载的数据长度 = ? 下载结束位置:(线程i

Android实现网络多线程文件下载

实现原理 (1)首先获得下载文件的长度,然后设置本地文件的长度. (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置. 如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示: (网上找的图) 例如10M大小,使用3个线程来下载, 线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M 下载开始位置:线程id*每条线程下载的数据长度 = ? 下载结束位置:(线程i

桌面应用也可以提供HTTP文件下载

以往,我们肯定知道,在搞Web应用的时候,我们都可能会遇到提供文件下载的功能需求 ,比如我以前做的一个客户许可证管理系统,客户购买ERP系统后,通常我们会根据客户的机 器的机器码生成一个许可文件,而这个许可文件是有时间限制的,一年后会过期,过期之后 客户需要重新获取许可,以表示继续使用我们的超级牛B产品,如果客户不再获取许可,就表 明客户不再使用我们的超级牛B产品. 后来一想,是啊,倒不如咱们弄个Web程序,让 客户自行登入,输入机器码后,自动生成许可文件,然后客户在页面上点击下载就行了.提 供

通过VS 2010+SVN为SQL Server提供数据库版本管理

对于一个软件企业来说,源代码就是公司全体智慧的结晶,绝不能有任何闪失.但对于公司产品的基石数据库怎么来 进行统一管理呢?通常,是直接备份数据库文件的方式,或者生成数据库的部署脚本,来重复的备份.这个方法可行, 却有些值得改进的地方.首先,太繁琐了,浪费精神:其二,不方便和其它项目同时管理.下面,就介绍我偶然看到的 方法,当然,可能有很多人已经这么做了,但分享给那些还不知道的人. 大体思路: VS 2010 提供了一个项目类型,新建->数据库->SQL Server 2005 数据库对象 或 S

基于select I/O模型的远程目录浏览与多线程文件下载

摘要: 服务器端基于select I/O模型.为防止程序界面阻塞, 有一个子线程用于不断接收socket并select其中的处理.客户端只有一个线程函数, 不过其利用率很高, 可用于远程目录交换, 请求文件大小, 创建若干线程来下载文件.文件传输有上传和下载,还有对等传输, 这个项目中, 传输文件具体指下载. 正文: 一 数据及数据结构 1 传送包, 客户端主线程给子线程传递的结构体 typedef struct{ char packetType; // 请求类型 r:request rootd

使用WebSphere Process Server提供的Business Space开发Mashup应用

简介: 本文基于 Business Space(业务空间)V6.2.0.1 版本的新功能,介 绍如何使用 JavaScript.Ajax.JSON 等技术来开发 Mashup 应用.通过本文, 读者可以了解 Business Space(业务空间)V6.2.0.1 的新特性,以及如何利用 这些新特性在业务空间中快速开发业务流程应用系统.. 引言 WebSphere 提供支持的Business Space(业务空间)是 IBM WebSphere BPM 产品线中重要的组件之一,它为广大用户提供了