大数据量报表技术研究
2013年03月19日

  大数据量报表技术研究


   

 

1 基本简介和说明................................................................................... 1

1.1  研究意义.......................................................................................... 1

1.2  研究范围.......................................................................................... 1

1.3  摘要和概述...................................................................................... 1

2 实用技术点........................................................................................... 2

2.1  理论基础-文件IO流和编码........................................................... 2

2.2  理论基础-基于互联网的数据传输................................................. 6

2.3  大数据量报表文件的生成.............................................................. 8

2.4  离线生成报表的机制.................................................................... 15

2.5  并发操作的控制............................................................................ 19

2.6  多线程生成报表的技术................................................................ 22

2.7  多服务器生成报表的技术............................................................ 24

2.8  实时下载技术................................................................................ 29

3 解决方案分类..................................................................................... 36

3.1  考虑数据量大小............................................................................ 36

3.2  考虑表现方式................................................................................ 39

3.3  工具插件的选择............................................................................ 43

3.4  数据库层........................................................................................ 46

3.5  服务器层........................................................................................ 48

参考资料................................................................................................. 51

附  录..................................................................................................... 52



1 基本简介和说明

1.1  研究意义

首在各大、中、小型项目中,数据的统计和下载被广泛的使用,几乎成了各个项目的必备功能。在很多系统中,数据除了在页面展示,还需要下载出来,用于统计、处理、备份,甚至再导入第三方系统中,涉及到的数据下载和统计报表就非常多。

对于报表的处理和展现,涌现除了众多的技术,同时也出现了很多难题:性能问题、功能问题、安全问题、开发成本问题,等等。在和客户交流的过程中,发现他们普遍对下载、报表统计不够满意,反映到报表内容不够灵活,格式不符合要求,下载速度慢,数据加工麻烦等等问题。同时在性能、功能、安全等上面,开发人员也面临着挑战。

本课题旨在对报表和大数据下载提供系统的解决方案,解决普遍性和关键性的问题,提高项目组中报表和下载模块的质量和开发效率。

 

1.2  研究范围

若非特别说明,本文的描述和结论都是基于一般Java Web应用的。非Java语言或者非Web应用领域,一般不属于本文的讨论范畴。

 

1.3  摘要和概述

要想解决数据下载和报表统计的问题,首先要以数据为切入点。数据有多少?涉及到数据的存储。数据如何存储?涉及到数据的结构和形态,重点在于数据库。数据如何加工和处理?涉及到应用程序和服务器。于是,本文从数据库、服务器、应用程序三个方面来分析和解决问题。

关于数据库,侧重研究数据的存储:对于大数据量,可以按日期、按类型分表;对于多表数据,可定期导入一张表中;对数据建立合理的索引,等等。

关于服务器,有需求和条件的情况下,可以进行多服务器部署,对于一服务器对应一数据源,多服务器对应单数据源部署技术进行研究。

应用程序方面,涉及的内容最多,也是本文研究的重点。通常来说,开发人员想要开发报表和数据下载功能时,要考虑数据量的大小、数据的表现方式、工具插件的选择、开发效率等问题,本文就是按这些点来分类,以便给开发人员提供清晰、系统的解决方案。

 

2 实用技术点

2.1  理论基础-文件IO流和编码

参见《Java文件IO流和字符编码》一节。

 

2.2  理论基础-基于互联网的数据传输

参见《基于互联网络的文件数据传输》一节。

 

2.3  大数据量报表文件的生成

一、问题的提出

w  报表文件的生成,性能的瓶颈在哪里?

 

二、问题的研究

       “报表文件的生成”,其主体是“数据”和“文件”,于是我们可以从“数据的读取”和“文件的写入”这两个基础进行考虑。

       当然,如果考虑得更具体的话,可能还会涉及到“数据的加工”和“程序的逻辑”,但是这两方面的复杂度是因需求的不同而异的,是未知的。因此,我们需要撇开这些差异因素,针对单纯的“数据库读取”和“文件IO”做一些测试。

 

       在数据量上,选取如下几个维度,来测试文件IO和数据库读取的性能,供参考。

w  数据条数:比如1000条、1w条、10w

w  数据字节数:比如100kb10mb100mb

 

1、文件IO写操作

1)测试环境:JDK1.6,笔记本电脑

2)测试描述:

w  采用前面所讲到的最佳方式——BufferedWriter的方式往文本文件里面写字符串

w  数据字节数一定的情况(3000万字节≈28.6MB),每次写N字节+循环M次,或者每次写X字节+循环Y次(其中N*M=X*Y=3000万)。

w  取测试100次的平均值

      

3)测试结果,如下图所示:

2.2 文件IO写操作测试结果

写循环的次数(万次)

每次写的字节数(byte

编码

耗时(毫秒ms

25

120

默认

133.81

200

15

默认

167.22

25

120

调用OutputStreamWriterGBK

212.02

200

15

调用OutputStreamWriterGBK

250.80

 

4)测试结果分析:

w  总体感觉,执行速度是非常快的,上百万次写入也就一眨眼的功夫。

w  减少写的次数、适当增加每次写的字节数可以略微提高效率,但几乎可以忽略不计。

w  字符编码的转换速度也很快,不会对整体效率造成质的影响。

 

2、数据库查询、数据读取

1)测试环境:Oracle10g数据库(局域网内远程连接)

2)测试描述:

w  采用标准JDBC方式连接和查询

w  简单SQL:有两个查询条件,均为字符串类型,都带有索引;查询字段大约60个,一条数据内容大小约0.34kb.

      

3)测试结果,如下图所示:

2.3 数据查询测试结果

数据量(条)

测试次数(次)

平均耗时(毫秒ms / 次)

1 4006

10

82552(约1.38分钟)

5 2522

3

299060(约4.98分钟)

10 1542

1

519961(约8.67分钟)

 

4)测试结果分析

w  总体感觉,相比文件IO写的速度数据库查询要慢得多,查询1万条数据时,就需要1分多钟了

w  查询耗时与数据量大体上成正比关系。

 

3、总结

 

       在正常的报表生成的过程中,“数据库查询、数据读取”比“文件IO写操作”的耗时要多得多,是性能的瓶颈所在。注意到,上面的测试还只是用的简单SQL去查询,如果是复杂的SQL,那么耗在“数据库查询”上的时间将会更多。

       无疑,“数据库查询,数据读取”这一块儿,是通常报表生成过程中最耗时的一块儿。如果能减少“数据的读取时间”,将能明显提高生成表报的速率。

 

       另外,以上的分析和测试,只对比了“文件IO”和“数据读取”这一块儿,需要补充一点的是,在报表下载中,还应考虑到“网络数据的传送速度”,这方面可能取决于网速。根据我在内网上做的测试,这方面速度是很快的,从这个角度来说,不会是性能的瓶颈所在。但是不知道在外网环境、网速不佳的情况下会不会拖后腿?而且通常,网络流量有限制,比如最大300kb/s,那么传输100M的文件就需要341秒!

       网络方面其实也容易出问题,例如,如果用户一次请求,程序执行“查询——生成报表——下载报表文件”整个过程,那么实际上这一个http线程可能会连接很长时间,如上面测试的那样,查询10万条数据就用了8.67分钟,那么http线程要一直连接至少8.67分钟,这是很慢的,而且http线程可能有超时设置,到了最大时间可能会自动断开。所以说,在涉及大数据量报表时,最好将“查询、生成报表”和“下载报表”的过程分开。

 

三、参考方案或建议

 

从宏观上看,要提高报表文件的生成效率,可以从以下几个方面着手:

w  优化查询语句

w   优化数据存储结构

w  优化程序算法

w  采用合理的报表文件格式

w  采用较好的生成报表的工具

 

下面一一来说明:

 

1、优化查询语句

从最实际有效的方式上讲,一次查询数据量不宜太多。如果条件允许,应尽量从设计上避免一次查询太多数据,例如采用分页查询、采用分批查询等,当然这个也得看具体的业务需求。

另外一点:优化好查询的SQL,比如索引等,性能也会得到巨大的提升,这方面可以参考“数据库优化”相关的资料,本文不做深入地介绍。

举个例子,看下面这个sql,这是我遇到的实际问题:

select count(*) from REPORT_TASK t where

 t.create_time>=to_date('2013-05-01', 'yyyy-mm-dd')

 and t.create_time<to_date('2013-05-05', 'yyyy-mm-dd')

最开始,查询速度非常非常慢。后来我在create_time字段上加了索引,就快多了。

总之,SQL是重点,“在开发报表的过程中,我们应该先把sql单独拿出来,调试好了之后才做进一步的开发,如果sql有变动,也要及时的做好测试”。

 

2、优化数据存储结构

       SQL优化的基础上,进一步可以考虑数据的存储结构、数据表的优化。

       举一个实际的例子:

       某个报表中需要有营业部ID、营业部名称,但是原数据表中只有营业部ID这个字段,没有营业部名称。原来的做法是根据营业部ID再到“营业部表”里面去查询营业部名称。这种方式就涉及到联表查询,效率很低。

如果在原数据表中增加“营业部名称”这个字段,并且在导入数据的时候就顺便导入这个字段的值,那么查询的时候就无需再从营业部表中去查,大大提高了效率。

 

       另外,还可以考虑建立一张中间表,把数据统一起来便于查询。通常,把那些查询得很频繁的数据,集中起来。

 

3、优化程序算法

       举一个实际的例子:

       项目中多用的是SqlRowSet,和其他很多类似的“RowSet”一样,它是把数据存入内存中,然后再用rs.next()去取数据。在开发和测试时,没发现任何问题,甚至在系统上线后一段时间内都没发现问题。但是后来发现生产服务器性能下降了。仔细检查才发现,原来是RowSet吃了大量的内存,要知道JVM占用的内存是有限的,超过了最大限度,则会发生gc,必然会大大的影响性能。但是,这个问题只有在数据量比较大时才会表现出来。

      

       我们经常的做法是把数据存入内存中,然后再执行其他操作,这样做可以增加处理速度、减少数据库连接时间。但是数据量过大时,这种做法不可取。

 

       可以通过Java JDBC自带的ResultSet去取数据,并设置合理的fetchSize,且ResultSetType要设置成Result.TYPE_FORWARD_ONLY类型(这也是默认的类型),这样就确保了ResultSet不会把数据都load到内存中。关于ResultSet的类型,有如下说明:

1 :ResultSet.TYPE_FORWARD_ONLY

    默认的cursor 类型,仅仅支持结果集forward ,不支持backforward ,random ,last ,first 等操作。 

 

2 :ResultSet.TYPE_SCROLL_INSENSITIVE

    支持结果集backforward ,random ,last ,first 等操作,对其它session 对数据库中数据做出的更改是不敏感的。

    实现方法:从数据库取出数据后,会把全部数据缓存到cache 中,对结果集的后续操作,是操作的cache 中的数据,数据库中记录发生变化后,不影响cache 中的数据,所以ResultSet 对结果集中的数据是INSENSITIVE 的。

 

3 :ResultSet.TYPE_SCROLL_SENSITIVE

    支持结果集backforward ,random ,last ,first 等操作,对其它session 对数据库中数据做出的更改是敏感的,即其他session 修改了数据库中的数据,会反应到本结果集中。

    实现方法:从数据库取出数据后,不是把全部数据缓存到cache 中,而是把每条数据的rowid 缓存到cache 中,对结果集后续操作时,是根据rowid 再去数据库中取数据。所以数据库中记录发生变化后,通过ResultSet 取出的记录是最新的,即ResultSet 是SENSITIVE 的。 但insert 和delete 操作不会影响到ResultSet ,因为insert 数据的rowid 不在ResultSet 取出的rowid 中,所以insert 的数据对ResultSet 是不可见的,而delete 数据的rowid 依旧在ResultSet 中,所以ResultSet 仍可以取出被删除的记录( 因为一般数据库的删除是标记删除,不是真正在数据库文件中删除 )。

 

另外,注意JDBC设置的一些默认值:(Orcale的驱动)


默认值

说明

ResultSetType

TYPE_FORWARD_ONLY

只能next读取

QueryTimeout

0

不限制

FetchSize

10

一次性最多读取10行数据

FetchDirection

FETCH_FORWARD

向前抓取

ResultSetConcurrency

CONCUR_READ_ONLY

只读型并发

ResultSetHoldability

HOLD_CURSORS_OVER_COMMIT

提交时仍保持游标

 

想更深入的了解,可参考如下的API

http://doc.java.sun.com/DocWeb/api/java.sql.Connection

http://doc.java.sun.com/DocWeb/api/java.sql.ResultSet

http://doc.java.sun.com/DocWeb/api/java.sql.Statement

 

对于大数据量报表数据的查询,其实上面这个fetchSize参数至关重要。下面给出了一个测试结果:

SQLselect * from b2b_ticket_stat t   where t.tkt_rcd_dt=? and t.userid=?

数据量()

fetch size()

测试次数

平均耗时(毫秒ms

73531

10

3

333264

73531

50

3

78797

73531

100

3

44227

73531

200

3

32090

 

程序关键代码如下:

   Connection con = dataSource.getConnection();

   PreparedStatement pstmt = con.prepareStatement(psql,

      ResultSet.TYPE_FORWARD_ONLY,

      ResultSet.CONCUR_READ_ONLY,

      ResultSet.CLOSE_CURSORS_AT_COMMIT);

   pstmt.setQueryTimeout(0);

   pstmt.setFetchSize(50);

      

       注意FetchSize越大,一次性load的数据就越大,意味着占用的JVM内存就越大,根据单条数据的大小(字节,比如0.2kb,那么10条数据就2kb了),确定合理的数据量,一般来说根据不同的情况,FetchSize效果的提升会有一个临界值,比如1000,超过这个临界值后效果提升就很小了。

       大数据量查询时,请特别注意上面这种写法,根据需要设置合理的FetchSize就可以了。

 

 

4、采用合理的报表文件格式

       这也是我们不得不考虑的一个额外因素。

       现在用得比较多的报表格式是Microsoft Excel的格式,xlsxlsx,然后是csvtxt,也可能有其他的要求,比如htmlxml、图片、pdfdoc等。下面简单介绍几个。

 

1csv文件

       csv是纯文本格式,并且csv是逗号分割式文件,用Excel打开时可以呈现类似于xls的表格效果。

       因为csv是逗号分割的纯文本文件,除了数据(全都是字符串)本身之外,没有任何附加信息,所以它不具备设置表格样式、数据类型、公式这样一些功能,但是csv有打开速度快、占用空间小、可以用Excel打开等优点。

 

2xls

       即众所周知的Microsoft Excel97-2003)工作表,功能很强大,支持表格样式、公式等等。但是因为它是专用格式,包含了很多附加信息,例如每个单元格的字体、颜色、数据类型等等,所以占用空间比较大,通常来说,相同数据量的情况下,比纯文本文件要大几倍,甚至更多。

       另外,xls文件最大只支持65536*256列数据,所以如果超过256列或者6万多行的数据,xls是无法保存的。

 

3xlsx

       xlsxxls的升级版,是从Microsoft Office 2007版起开始启用的新格式,这个格式其实是一个基于XML的压缩文件格式,把它的后缀改成zip打开可以看到,里面有很多的xml,它比xls占用的空间稍小,支持最大行数是1048576行,最大列数是 16384列。但是相当于纯文本来说,它占用的空间还是非常大的,而且只有Microsoft Excel 2007及其以上的版本才支持打开xlsx文件。

       另外注意到,虽然xlsx支持上百万行的数据,但是如果真要打开一个上百万行、几百MB的文件的话,会把Excel软件给撑死的,对于这种超大的数据量的文件,还是纯文本的处理效率高一点。

 

 

5、生成报表的工具

       如果是txtcsv等简单格式,其实可以不需要工具,但是像xlspdf等,可能就需要借助一些插件或工具来完成了,常用的比如BirtJasperReportsiReportJFreeReport,也可以基于较底层的Jakarta POI API去开发。在后续章节中,我们会对这些报表工具进行更详细的介绍。

 

 

2.4  离线生成报表的机制

一、问题的提出

w  如何减少用户下载报表的等待时间?

w  如何提高报表的生成效率?

w  如何对报表下载实行统一的调度?

w  如何实行报表文件的多次下载?

w  如何对报表文件进行统一的管理?

 

 

二、问题的分析

 

1、如何减少用户下载报表的等待时间?

       一般的做法是,用户提交一个请求,然后程序去查询数据,然后生成文件,最后再把文件返回给用户。如果这个过程时间很长,那么用户需要在页面上等待很长一段时间,比如10分钟甚至更长,页面一直卡在那里,还不能离开,不能做其他事情,用户会疯掉的。

       因此,如果能实现一种全新的模式,“让用户只需要提交一个请求,然后可以离开当前页面去做其他事情,甚至退出登录”,那将是多么美好的事情!所谓的“离线生成报表技术”就完成了这样一个目的:它接收用户的报表请求,然后立即返回,告诉用户“OK,您的请求已接收,正在处理中”。报表的生成过程完全在服务器后端执行,执行完成后才通知用户。

 

2、如何提高报表的生成效率?

       从普通角度来说,需要提高生成报表的速度,除了各种优化之外,还可以专门为报表制定多线程去处理(充分利用CPUIO),进一步讲,还可以用多进程、多服务器去处理,成倍地提高报表生成的速度。从另外一个角度来说,优先服务“优先级较高的报表”,也是一种侧面提高效率的方式。

      

3、如何对报表下载实行统一的调度?

       要实现统一调度,首先要对用户发起的报表请求进行统一管理。分析用户的请求,根据特定标识(比如用户ID、报表类型等等),制定不同的并发数和优先级,按一定的策略制定报表执行的先后顺序。

 

4、如何实行报表文件的多次下载?如何对报表文件进行统一的管理?

       通常情况下,用户的每次请求都要去生成一次报表,然后供用户下载,报表文件是一次性使用的,服务器端不会保存这个报表文件。能不能把报表文件保存起来,用户需要可以随时去下载?比如用户需要对比现在这个报表和10天前的报表的数据,但是用户自己通常不会保存10天前的报表,所以他可能还会去下载一次。每天下载一个报表,积累7天,就可以形成一个“周报表”,实际上这就是用户需要的。

   报表文件的统一管理,比如怎么存储(怎么定义文件名和目录)?怎么控制操作文件的权限?怎么控制文件的保存时间(比如最多只保存3个月)?实际上,只要制定一套规范标准,按照这个定义去执行就可以了。

 

 

三、离线生成报表的实现

 

这一套流程如果要完全实现,还是有些复杂度,在此,只对其中的关键点进行介绍。

 

1、对用户提交的报表请求进行统一的管理

       基本思路:将每一个请求定义成一条“任务(task)”,再把这个任务附加上一些参数,再存入数据库中。

 

1)关键问题一: 如何定义一条任务?

       用户提交的每一个报表请求,通常都包含了一些限定条件(查询条件),有些限定条件是用户在页面输入的查询条件,有些限定条件是从服务器端根据用户标识等默认自动加上去的。那么我们就需要把这些数据封装起来,形成一条数据,插入到数据库中。

       XML是一种比较好的组装和解析数据的方式,同时也支持较复杂的数据结构,我们可以将页面表单信息,包括服务器端的一些信息组装成xml,然后存入数据库中,这样就形成了一条task

2)关键问题二:应该为任务附加哪些参数?

       除了上面那些查询条件之类的信息,还需要附加一些额外的信息,用作调度控制用。比如任务编号,给每一个任务一个唯一的标号。还比如serviceID,代表处理这个task对应的服务程序。还比如priority,代表这个任务执行的优先级。status,代表这个任务的状态,等等。

 

2、对报表的生成进行统一的调度处理

分成如下几个模块:

       任务装载——任务分配——任务执行——任务更新

 

1)任务装载

 

如图所示:

clip_image007.png

       主要是一个从数据库中查询数据的过程。按照一定的条件,比如每次查询出10task数据,隔1分钟检查一次这10条任务执行完没有,如果执行完了,这再查询出10task。查询出来的数据,要更新它的load_status状态,比如load_status=0表示未装载,load_status=1代表已装载。甚至还可以做一个机制,装载任务的时候将taskNo存入服务器端文件中,如果数据被装载了,但是由于服务器强制重启或者挂掉,任务没有被执行,那么服务器启动的时候首先从本地文件中读取taskNos,然后检查这些taskNos是否都已经执行完成,如果没有执行完成,则再次load这些数据。

 

2)任务分配

 

如图所示:

clip_image009.png

       对已装载的任务分配worker去执行它(worker代表实际去执行这个任务的线程)。这有一个简单的调度过程,先把装载好的任务放到一个waitList里面,比如一共有10worker可用,那就分配10task,但是如果只剩5worker可用,那么就只能分配5task,剩下的task将进入等待状态。同时,按照一定的规则顺序将waitList里面的数据分配到runningList里面去,比如某个任务要在13:00执行,它会提前一点时间进入waitList,然后到了13:00,才给它分配worker

 

3)任务执行

 

如图所示:

clip_image010.png

       每一个task对应一个serviceID,代表了这个task需要哪一个service为它服务(就是说具体的生成报表的逻辑由一个对应的service类去处理),worker线程的作用只是去调用serviceID指定的service类执行处理逻辑。

 

4)任务更新

 

如图所示:

clip_image012.png

       任务何时开始执行;何时执行结束;如果出现异常,那么发生异常的时间和原因等,都需要及时的更新到task表中,这时就需要一个任务更新的模块来执行这些工作。在任务分配、任务执行的关键时间点上,会向任务更新模块传递任务执行的信息,任务更新模块接收到了这些信息后,就去执行更新数据库的操作。

 

 

2.5  并发操作的控制

一、问题的提出

w  如何控制某个功能的访问并发数?

w  如何避免高并发引起的处理压力?

w  如何针对某个功能进行“服务优先级”控制?

 

二、问题的研究

       Web服务器有一个最大HTTP线程数控制,但是这个是全局的,无法针对某个功能点进行并发控制。举例来讲,假设订单查询是很耗资源、很费时的操作,所以在同一时刻,我们最多只允许有10个订单查询的请求,怎么来控制?

       显然,这个功能是和具体的业务要求相关的,所以基本思路是通过应用程序来控制。比如说写一个过滤器Filter,对这种请求进行检查和处理。由此,我们引入一种手段,称之为“并发锁机制”。

 

1、并发锁机制原理

       对于具有某个特定标识(比如用户ID)的访问,记录其访问的次数。访问开始计数器加1,访问结束计数器减1。当计数器等于N时,代表此时有N个请求正在处理中,如果N大于我们定义的最大并发数,则立即拒绝进一步的处理,返回并告知访问者“当前服务正忙,请稍后再试!”

       对于不同的访问标识,定义不同的最大并发数,通过这种方法可以实现“限量访问”、和“优先级控制”。假设用户AB的最大并发数分别定义为510,分析如下:

 

1)非抢占规则

       假设正在处理的AB用户的请求数均为0,然后来了8个用户A的请求和8个用户B的请求。那么,按照“非抢占规则”,此时有5个用户A的任务和8个用户B的任务能得到处理,剩下的3个用户A的任务将被拒绝。

       也就是说,A的任务只能占用A那固定的5个并发数名额;B的任务只能占用B那固定的10个名额。它们不能够互相抢占对方的并发数名额。

       在这种方式下,AB的任务都可以得到处理,只是处理的数量不同而已。

 

2)抢占规则

       用户AB有优先级关系,假设用户B的优先级比用户A要高,那么用户B的请求可以获得优先满足,并可以占用A的并发数名额。但是,如果用户A的请求达到了最大并发数,则会被拒绝。

       其逻辑可以用如下的代码来描述:

// X代表一个请求

if(X==B)

       if( runList.size()>(MB+MA) ){ //达到最大并发数MB+MA,则拒绝

              throw new ConcurrencyException();

       }else{ //否则执行B的任务

              runList.add(B);

              //…doService 结束时runList.remove(B)

       }

if(X==A)

       if(runList.size()>MA ){ //达到最大并发数MA,则拒绝

              throw new ConcurrencyException();

       } else{ //否则执行A的任务

              runList.add(A);

              //…doService 结束时runList.remove(A)

       }

 

2、并发锁机制的实现

 

如下图所示:

clip_image013.png

       详见附件的Demo程序。

 

3、优先级控制

1、非阻塞模式(把任务交给线程去调度):

       将数据封装到Worker中,加入到有序的队列中,控制线程按顺序去取出Worker,然后启动线程去执行。

2、阻塞模式(循环等待,直到执行或者超时)

       来了一个请求,首先加入到排队队列中,如果排在前N位,则执行,否则,等待、再检查是否排在前N位,如果排在N位,则执行,否则,再等待……如此循环。

 

常用的阻塞模式实现如下所示:

clip_image015.png clip_image017.png

 

 

2.6  多线程生成报表的技术

一、问题的提出

w  多线程如何提高报表的生成效率?

w  如何设计处理报表的多线程程序?

 

二、问题的研究

 

1、多线程如何提高报表的生成效率?

       普通电脑上的多线程实际上是串行执行的(例如我笔记本WIN7系统,2CPU,但是执行多线程程序的时候,仍然是串行执行的,CPU在多个线程中是来回切换执行,同一个时间只能有一个线程获取CPU),传说某些AMDCPU可以提高一些多线程处理的能力。其实,可见多线程的调度和操作系统、CPU有关系,具体的效果要看环境,可以做一些测试。

       现在很多的生产服务器,是多个CPU(注意是多个CPU而不是单个CPU多核心),那么在这种环境中,就能体现出多线程的优势。特别是多线程在执行一些IO操作时,性能能够得到很大的提升(如果没有IO操作,而主要是靠CPU的计算,那么多线程优势可能不会有这么明显)。总之来说,多线程是提高报表处理能力的一种途径。但是其效果要看程序具体的运行环境,这个“多”线程到底要“几个”线程才是最有效的,最好要通常实际的测试来确定。

 

多线程与其他环境的关系图:

clip_image018.png

总结,多线程的优势:

性能提高关键之处一:

         多数据连接、数据装载并发执行。

性能提高关键之处二:

         IO、文件生成并发执行。

性能提高关键之处三:

         如果是多CPU,则可以实现并行计算处理。

 

2、如何设计处理报表的多线程程序?

       这一方面可以直接采用Java的基础API来实现,Java对于线程的操作比较简单(但是不好控制),关键是要对多线程进行合理的控制——Java多线程编程。

1)关键点一:如何合理的分配线程

       首先,对线程的数量进行控制,应该有一个最大线程数N。这个N要根据具体的环境确定,比如N=1或者N=100效率可能都较低,那么N等于多少时效率是最高的,要根据所在的环境去测试来确定。第二,任务按照何种顺序、优先级来分配线程,应该有这样一个调度程序,来执行分配线程的工作。

2)关键点二:对线程进行监控

       线程分配后,应该把它的对象的引用(句柄)保存起来,比如放到一个Map中,这样就可以随时观察和控制这个线程。例如Map里面有一个thread01,调用这个thread01.getState(),就可以知道这个线程的状态。还可以保存这个线程的启动时间等等。有这样的一个监控体系,才能够适应比较复杂的应用场景。

3)关键点三:如何处理异常的线程

       单个线程是独立的,一旦挂掉,如果不做处理,可能会带来一些麻烦。所以线程内部应该捕获异常,在生成报表的过程中,如果出现了异常,则转而执行对应的异常处理流程。但是有一种情况比较特殊,比如一个报表任务或者一个线程,它的最大执行时间是限定了的,比如我们可以设定执行报表任务的线程最多只能执行60分钟。如果这个线程的执行时间超过了60分钟,则视之为出现了异常,此时需要强行的结束这个线程。

4)关键点四:注意数据的同步

       每个线程所执行的task,都应该是不重复的,也就是说“一个线程处理一个task,不至于某个task被多个线程同时执行”。这个主要是控制在数据装载的时候。通常来说负责数据装载的线程只有一个,负责线程调度的线程也只有一个,负责执行任务的线程有多个。

 

报表系统多线程关系示意图:

clip_image020.png

总结:

1、为什么要采用线程池?

      java.util.concurrent.ThreadPoolExecutor

2、设置合理的线程数,需根据CPU数量以及程序(数据库、IO)等实测去确定。

3、适当的分层、合理的统一,解决线程、数据同步问题,设计和算法是关键。

4、监控机制(线程、数据)——可视化监视,可手动控制。

5、注意异常线程的处理,比如达到了timeout的线程如何处理?

 

 

2.7  多服务器生成报表的技术

一、问题的提出

w  如何进一步的提高报表的生成效率?

w  如何设计多服务器生成报表的应用程序、系统架构?

 

多服务器系统示意图:

clip_image022.png

 

 

二、问题的分析

 

1、为什么要用多服务器?——服务能力分析

 

案例:

1)实现针对手机终端的信息推送功能,支持终端量级为千万级。(来源:oschina

分析:

       如果用TCP长连接实现,单台20万长连接,支持千万级别只要50台机器。

 

2)报表引擎,每个应用服务器开设10个线程处理报表,共3台服务器。

分析:

       假设一台服务器平均每2秒执行完1个报表,3台服务器一分钟可执行的报表数量为90个,执行完1000个报表要11分钟,1万个报表要111分钟。

 

 

       面对超大的服务压力,那我们应该怎么办?

       1、改善系统架构。(后面会讲到)

       2、增加服务器。(就是本节所讲的内容)

       3、……

 

2、如何进一步的提高报表的生成效率?

       上面提到一点:“多线程”,但是也说明了,多线程的效率可能因为环境的不同而有所差异,通常来说,多线程能提高的效率比较有限。如果是多“进”程,则效率提升会更明显,几乎能成倍地提高效率。而放到“多服务器”的层面,通过集群的方式,则效率提升更加明显。

       多服务器(或者多进程)所面临的关键问题:如何保证一条task只能被一个服务器装载(因为它被一个服务器装载之后,其他服务器就没必要再去处理它了),进一步讲就是“如何保证一条task不会被多个服务器同时更改,多服务器之间如何进行数据的同步?”

 

3、如何设计多服务器生成报表的应用程序、系统架构?

 

从系统架构上讲,有如下两种方式:

架构一:多应用服务器 + 同一套数据库( + 共用文件存储空间)

clip_image024.png

架构二:(一个应用服务器 + 一套数据库)×N

clip_image025.png

 

方案一的优势:

       >数据和文件都得到了统一,统一调度、统一管理;

       >AppServer可以方便的平行扩展。

 

方案二的优势:

       >一台服务器对应一个数据库,数据库资源独享;

       >多服务器之间完全独立,不需要同步。

 

       综合分析,方案一比较好,但是要解决“多服务器之间如何进行数据同步”的问题,后面将做详细介绍。

       另外,可以将架构一和二结合起来,将架构二中的AppServer换成Cluster,每个Cluster采用架构一的类似设计,这样就可以结合二者的优势。

 

 

三、多服务器生成报表的解决方案

 

       需要解决的关键问题是:数据如何在多服务器之间保持同步?

       即“如何确保一条task只被一个服务器装载?”

——解决思路:

              1、控制数据的装载(读取)入口,避免同一数据被多个服务器装载。

              2、采用状态位或者“模拟锁”的方式,控制多服务器共同修改数据。

 

1)关键点一:合理的进行数据装载

       如果每个服务器都去查询task,每次查询10个,那么可能服务器A查询出来的task,同时也被服务器B查询出来了。最容易想到的方法是:服务器A查询出10条数据,然后把这10条数据的load_status状态改成1(“已装载”)。这种简单的想法是不太实用的,因为服务器A查询10条数据改状态,服务器B也可能在这同一时刻查询出这10条数据改状态,如果服务器A改状态成功了8条,服务器B改状态成功了2条,这通常是一个批量更新的过程,那么怎么判断是哪几条数据状态改成功了、哪几条数据状态更改失败了?而且这服务器AB之间相互抢占数据的频率是很高的,上面这种情况经常发生。

       解决方案:采用“模拟锁”。上面那种简单的想法其实也管用,但就是有一个很大的缺点:所有的数据都会被多个服务器抢占。其实,可以改进一下: 只抢占一条特定的数据(称这条数据为“锁”),如果服务器A抢占成功了(我们称之为“拿到了钥匙”),就可以继续去查询其他数据,反之,如果它没有抢到“钥匙”,就需要进入短暂的等待。

 

模拟锁实现原理:

       见如下的代码描述:

public void loadTaskData(int queryNum){

       // START - 开始事务 (Springxml中配置的,声明式事务)

       // 也可以用编程式事务

      

       // 拿钥匙

       taskDAO.updateDBKey();

       //"select '' from report_config t where t.key='DB_OCCUPY_KEY' for update"

      

       // 查询数据

       List<Task> queryList = taskDAO.queryTasks(queryNum);

      

       if(queryList==null || queryList.size()<1){

              return;

       }

      

       // 更改taskSet数据在数据库中的状态

       boolean success = taskDAO.updateScheduledStatusBatch(queryList);

       if(!success){

              // 抛出异常,事务回滚

              throw new RuntimeException();

       }

      

       // TODO 装载数据(存入taskSet)供使用。

       for(Task task:queryList)

       {

              task.setStatus(MainConfig.TASK_STATUS_SHEDULED);

              ControlCenter.taskList.add(task);

       }

             

       // END  - 提交事务

}

 

2)关键点二:多服务器部署和配合

       对多服务器的部署并没有要求,独立部署、集群部署都可以。建议在多应用服务器的基础上见了一套web服务器,通过web服务器来统一访问各个应用服务器。建议多个服务器采用同一个数据、共用所有表。另外,建议“不同的服务器生成的报表文件最好是放在统一的地方”,也就是说多个服务器要有一个共享的存储空间。这样从多个服务器都可以直接操作这些文件,便于管理、也便于下载。举个例子,如果服务器AB生成的报表文件发到各自的磁盘目录下,那么用户如果访问A服务器但是要下载B服务器上的报表则会出现困难。当然,也有解决办法,就是在服务器生成一个报表文件时,标明它是在哪个服务器生成的以及它的物理存放地址,当用户下载报表时,就返回一个下载的链接,这个链接直接指向报表文件所在的服务器的地址。

       关于负载均衡,对于外部应用的访问请求(比如新增报表的请求),如果有web服务器,则通过web服务器来做这一块儿的负责均衡。建议一定要建立web服务器。万一没有web服务器,则可以模拟web服务器的访问分发机制,做一个简单的、比例比较均衡的随机访问机制。对于生成报表方面,上面说到了“模拟锁”机制,其实在“模拟锁”机制上稍微优化一下,则可以做到task的公平分配。每个服务器都定时轮流查询数据库装载数据,限定每个服务器最多只能拿多少个task,比如10个,如果服务器A拿满了10个,则让它等待一段时间,把机会让给其他服务器。

 

 

2.8  实时下载技术

2.8.1下载概念

    下载(xià zài)是指通过网络进行传输文件,把互联网或其他电子计算机上的信息保存到本地电脑上的一种网络活动。下载可以显式或隐式地进行,只要是获得本地电脑上所没有的信息的活动,都可以认为是下载,如在线观看。

 

2.8.2下载方式

    WEB下载方式分为HTTPFTP两种类型,它们分别是Hyper Text Transportation Protocol(超文本传输协议)File Transportation Protocol(文件传输协议)的缩写,它们是计算机之间交换数据的方式,也是两种最经典的下载方式,该下载方式原理非常简单,就是用户两种规则(协议)和提供文件的服务器取得联系并将文件搬到自己的计算机中来,从而实现下载的功能。

    BT下载实际上就是P2P下载,该种下载方式与WEB方式正好相反,该种模式不需要服务器,而是在用户机与用户机之间进行传播,也可以说每台用户机都是服务器,讲 "人人平等"的下载模式,每台用户机在自己下载其它用户机上文件的同时,还提供被其它用户机下载的作用,所以使用该种下载方式的用户越多,其下载速度就 会越快。

    P2SP下载方式实际上是对P2P技术的进一步延伸,它不但支持P2P技术,同时还通过多媒体检索数据库这个桥梁把原本孤立的服务器资源和P2P资源整合到了一起,这样下载速度更快,同时下载资源更丰富,下载稳定性更强。

 

2.8.3 下载工具

1) 使用浏览器下载

    这是许多上网初学者常使用的方式,它操作简单方便,在浏览过程中,只要点击想下载的链接(一般是.zip.exe之类),浏览器就会自动启动下载,只要给下载的文件找个存放路径即可正式下载了。若要保存图片,只要右击该图片,选择“图片另存为”即可。

    这种方式的下载虽然简单,但也有它的弱点,那就是不能限制速度、不支持断点续传、对于拨号上网的朋友来说下载速度也太慢。建议初上网的网友选择这种方式。

2) 使用专业软件下载

    专业软件使用文件分切技术,就是把一个文件分成若干份同时进行下载,这样下载软件时就会感觉到比浏览器下载的快多了,更重要的是,当下载出现故障断开后,下次下载仍旧可以接着上次断开的地方下载。

3) 通过邮件下载

    此方式可能是最省事的了,你只要向因特网上的ftpmail电子邮件网关服务器发送下载请求,服务器将你所需的文件邮寄到你所指定的信箱中,这样就可以像平时收信那样来获得所需的文件了。我们可以采用专业的邮件下载工具,如Mr cool、电邮卡车E-mail Truck等,只要给它一个文件下载地址和信箱,剩下的就可由它总代理了。此方式也有很多不足之处,一是由于邮件下载是有排序性的,只有将把在你之前的下载请求全部完成后,才能轮到你,这就会影响到文件的时效性;另一个就是使用E-mail传送文件时需要重新编码,所以收到的文件要比直接下载的大一些。

 

2.8.4 HTTP下载原理

    浏览器与服务器通过TCP/IP协议建立Socket连接,然后通过Socket进行数据传输来实现下载功能。用户每次通过浏览器向服务器发送请求以及接收响应时,这个过程本身就是下载,在下载的概念定义中也说明了这一点。

    HTMLCSSJS、多媒体等文件也同样是通过Socket实现从服务器到浏览器传输。但是数据类型有很多,那么浏览器是如何处理不同数据类型的文件?这时候重点看URL的协议是什么,假设URL是以http打头,那么浏览器就用http协议解析内容。解析完毕后,浏览器根据Content-TypeContent-Disposition来决定文件是显示还是下载。

Content-Type:指明传输文件的媒体类型。

    Content-Disposition:此关键字可以告诉客户端如何处理我传给你的内容。response.setHeader("Content-Disposition", "attachment;filename=文件名")attachment表示以附件方式下载。如果要在页面中打开,则改为inline。协议规范中标明:disposition-type = "inline"/ "attachment"/ extension-token;    values are not case-sensitive。(具体可参考RFC 2183规范)

 

2.8.5 HTTP下载实现方式

    目前常见的有如下两种下载方法实现:

       A)从Response中获取ServletOutputStream流,利用这个输出流可以将数据写入到客户端。

       B)应用服务器本地生成文件,然后将请求重定向到此文件URL地址,如:response.sendRedirect("/${上下文根}/${文件名}")

 

2.9.14 项目解决方案

1SQL语句:加上where条件过滤、加索引;

2、生成文件时,先在本地生成,然后再向客户端发送此文件的服务地址;定期删除文件。(可以解决多次点击下载按钮引起socket error)(可选);

3、加上65536条记录判断,每65536条记录新建一个sheet(建议明确需求,根据需求决定);

4、建议数据量大时用htmlcsv格式生成文件,可降低OOM风险(建议明确样式后,在决定。可选);

5、多线程访问时,日志记录混乱,可以先缓存一次输出或者每条记录加上唯一标识;

6、数据库连接应增加限制,避免影响其他核心业务表的性能。

 

2.9.15 大数据量下载解决方案总结

       服务器优化:增加连接数、增加服务器内存、服务器集群、开启服务器缓存

       数据源优化:读写分离、数据库集群、针对系统业务实现数据库(针对某个系统业务逻辑优化)、SQL优化、与业务表分离

       应用优化:离线下载、多线程并发、独立报表应用、压缩文件、采用存储效率高的文件格式、预先下载、已下载文件临时缓存到硬盘、尽量减少数据二次处理耗时、异步处理降低阻塞时间

       业务优化:优化业务流程、避免生成无效数据

       其他优化:GC优化、实现下载客户端、采用其他协议实现下载

 

 

3 解决方案分类

3.1  考虑数据量大小

数据量大小的定义方式:

1)数据的多少:数据条数,单位为“条”

2)数据的大小:数据占用的存储空间大小,单位为“字节byte

 

数据量的几个阶段:

1)数据源:涉及到的总数据量,可供查找的所有数据。

2)加工过程中的数据:实际用到的数据,比如查询出来的数据,可供进一步处理。

3)最终报表中的数据:写入报表(文件)中的数据,加工后的最终结果。

 

3.1.1 从数据源、总数据量上考虑

       这个主要是一个查找、数据装载过程,也是报表系统中最耗时的阶段。从三个特殊例子来分析:

(1) 1千万数据中查找一条数据

(2) 1数据中Load出全部数据。分每条数据10 byte300 byte两种情况。

(3) 1千万数据中查找一万条数据。同样,考虑到单条数据的字节数。

       对于(1),总数据量是庞大的(1千万条),但是查找出来的数据却只有1条,这种情况主要考虑一个查找速度。如果查询很复杂,且数据量越大查询速度直线下降或者成对数型下降,那么这个数据量就是需要我们重点考虑的问题了。但是通常情况下,比较成熟的数据库,利用索引等技术,查询速度是非常快的。

       结论:总数据量对于查找速度的影响,取决于查找所使用的算法。我们可以根据实际效果去确认。下面我给出了一个实例供参考:

---案例一

--- Oracle10g远程数据库,b2b_ticket_stat表,共2200W数据

---select * from b2b_olap.b2b_ticket_stat t where t.rec_no='RN201301200012462338';

---其中rec_no字段上加有普通索引,P/SQL上显示耗时是0.265秒。

---可见,数据总量是很大的,但是对查询速度的影响较小。

---

---案例二

--- Oracle10g远程数据库,report _task表,共7W数据

--- select * from report_task t where t.tcode='HEAABC';

---其中tcode字段无索引,耗时29.546秒。

---可见,查询速度很慢,数据总量对查询速度影响大,如果数据有2000W的话,耗时将无法忍受。

 

 

 

 

       对于(2),撇开查询的时间不计,重点考虑数据的装载速度,即“将数据从一个地方,复制到另一个地方”,这个速度和数据的大小(字节)有关。也就是说,传输10 byte的数据和传输300 byte的数据,耗时是不一样的,而且差别比较明显。另外,从文件中读取数据和从远程数据库中读取数据,速度也是不一样的,文件读取的速度是非常快的。但是一般我们考虑从数据库中读取数据,我做了一个实验:

3.1 从数据库中装载不同大小的数据的速度

读取的记录总数

单条记录的大小(byte

SQL

耗时(毫秒ms

fentchSize 10 / 200

151

70

select   * from tvb2b_office t

1700 /   950

1943

70

select   * from hob2b_office t

10230 /   1540

149

350

select   * from b2b_ticket_stat t where t.tkt_rcd_dt=?

2181

1231

350

select   * from b2b_ticket_stat t where t.tkt_rcd_dt=?

7454

       可见,装载的数据字节数越大,耗时就越久,增幅不大,但是也不小,为比较平稳的线程增长,但是积累一定量之后,差距也是非常明显的,正所谓量变引起质变。

       总结:数据的大小(字节)是影响数据装载的关键因素,数据记录越多、单条数据byte越大,装载数据的时间就越慢。

 

       对于(3),既要考虑查询速度,又要考虑数据装载速度(主要是网络数据的传输速度)。假设在查询速度很快的情况下,那么主要就是一个数据大小的问题。假如查询出来的数据本身并不大,但是对查询速度要求较高,那就要考虑查找算法和总数据记录多少的问题。

 

 

3.1.2 考虑加工过程中的数据量

       加工中的数据的多少,对于数据处理速度的影响,这个往往取决于具体的处理逻辑。如果处理逻辑很复杂、耗时,那么数据越多,耗时越明显。反之,如果处理逻辑很简单、处理速度很快,那么数据即使很多,耗时也不是很明显。

       总结:针对数据处理的复杂度,来考虑加工中的数据量。下面举一个实际的例子供参考:

---案例一

---业务要求:查询出指定订单,封装到LIST<VO>中,传送到另一个地方,然后再对这些数据进行遍历和处理,处理过程比较复杂,涉及到内部的遍历、排序和查找。

---特点:加工中数据的多少,对整个流程的整体耗时影响较大,且如果加工中的数据过多,还有造成JVM内存溢出的风险。

---

---案例二

---业务要求:查询出指定订单,然后经过简单的格式处理,直接生成TXT报表

---特点:加工简单,一个while循环,数据量再多,加工的时间也很短,故此处无需考虑加工中数据量的大小。

 

3.1.3 考虑最终报表中的数据量

       直观上讲,考虑“最终报表中的数据量的级别”,数据条数是“100+”、“1W+”还是“100W+”,或者数据字节数,是“1KB+”、“1MB+”还是“100MB+”。

       例如,我要做一个报表,就要考虑单个报表可能达到的最大数据量有多少,如果只是一个“汇总统计表”,数据条数在1000行以内,那么表现层上,可以使用xlscsv等格式都可以。如果有个“订单明细表”,假设数据条数可以到达“100W”条,数据字节数可以达到“300MB”,那么就要考虑用txtcsv格式,甚至文件分割等方式了。

 

 

 

3.2  考虑表现方式

       大量的数据存储或展示必须要考虑到文件存储格式,这样就引出了两个大问题:以何种方式存储?如何保存?

       上述两个问题看似简单实则有深意。我们可以把问题转化为下述几个问题:

       1、数据存储什么样的格式便于数据分析、处理?(结合上述特点决定)

       2、大量数据处理非常消耗内存,如何避免内存溢出?(少用对象,多用字符串,能及时释放内存。一般采用htmlxmltxtcsv格式解决)

       3、当前格式的文件限制条件是什么?(上述特点描述中有答案)

       4、如何降低文件大小利用有限的宽带进行传输?(压缩)

       5、超过当前文件限制的大小后如何处理?(文件切割打包,xlsetxlsx创建多个sheet

3.2.1文件分类

       我们常常把文件分成两种类型,一种是表格文件,另一种是图形文件。下面是列举的常见文件格式:

       常用的表格文件格式:txtcsvxlsxlsxethtmlxml(适合大量的数据存储)

       常用的图形文件:htmljbpgpngbmpgif(渲染图片时非常消耗内存,不适合大量数据存储,只适合统计结果展示)

 

3.2.2 如何决定存储格式

       1、要考虑用户的使用方式,首先判断是否有浏览需求;

       2、如果不需要浏览,只是用来保存或者分析,建议采用专业的数据存储文件存储,如数据库备份文件;

       3、如果需要浏览,那么需要考虑采用什么方式浏览,一般有三个方案:

l   操作系统内置软件浏览

l   需要安装软件进行浏览

l   需要提供特有的界面进行浏览

       4、结合各类表格文件特点来决定采用何种格式存储,详见3.2.3章节。

 

3.2.3各类表格文件特点

       xlsxlsxet格式研究:初始大小7KB,增量为0.5KB。存储相同值是存储一次+引用方式,而非多次存储相同值。需要注意的是即使采用此文件每个单元格都存储相同的值也比不上txt文本格式存储利用率,因为每增加一个单元格会消耗大于“相同值引用”的开销,即占用更多的存储空间。

       csvtxtxmlhtml有多少字符就占用多少字节,中文两字节,英文一字节。如8192字节存储到csv文本中,则csv文本大小为8kB,如果是存储为xls可能就是十几kB

事实上htmlxlsxlsxet的本质都是xml

u  TXT存储规则

  有多少字节就占用多大的空间。一般情况下采用字符串拼接方式进行存储,占用内存低,可及时flush清除内存,降低内存占用。

  限制条件:无样式、字符串拼接较为复杂、阅读困难

  优点:不需要安装软件即可打开、空间利用率最高、只有存储空间大小限制

使用场景:

1、不需要样式;

2、存储利用率最高;

3、占用内存最少;

4、不采用逗号分隔(csv格式是采用逗号分隔)

 

u  CSV存储规则

       限制条件:无样式、字符串拼接较为复杂

       优点:不需要安装软件即可打开、空间利用率高、只有存储空间大小限制、用Excel软件打开方便浏览(但有Excel版本限制)、占用内存低

       使用场景:

1、不需要样式;

2、存储利用率最高;

3、占用内存最少;

4、用户用Excel软件浏览(存在问题是:超过指定Excel版本限制后,可以继续存储,但是用Excel软件打开只能显示前65536行或者1,048,576 行,无法分sheet)。

u  XML存储规则

       限制条件:需要第三方软件或者自主实现来展现样式,占用无效字节多;

       优点:易于第三方系统读取数据、跨数据交互平台、表现出复杂的数据结构、只有存储空间大小限制

       使用场景:需要存储复杂的数据结构,需要后期数据处理,跨平台传输,已有样式实现的系统。

       备注:如果需要表现复杂数据结构,且存储利用率最高,可采用json存储为txt实现。

u  HTML存储规则

       限制条件:占用字节多;不利于第三方系统读取数据、HTML格式复杂

       优点:已实现丰富的样式、可存储复杂的数据结构、可用Excel应用程序展示、可及时释放内存。

       使用场景:采用第三方jar生成xlsxlsx文件时内存溢出;用户需要用浏览器浏览;需要展现丰富的样式。

u  XLS /ET存储规则

       限制条件:需要安装WPSMicrosoft Office;学习成本高开发复杂;占用内存较高;工作表大小 支持65,536 行乘以 256 列(超过时无法存储)

       优点:样式丰富,易于阅读

       使用场景:样式较复杂时,样式部分可用宏命令(VBA)在Excel中实现;用户安装Excel软件;需要用Excel进行数据处理(这部分服务完全可以交给应用解决,降低人工成本);用户习惯用Excel浏览、保存。

       备注:POI的低版本jar包占用内存较高,无法及时释放内存,数据量较大时易出现内存溢出。3.9版本采用新对象解决。

       支持软件:Jakarta POIJExcelApi

u  XLSX存储规则

       限制条件:需要安装WPSMicrosoft Office2007及其以上版本);采用特殊Jar包开发需要学习成本;占用内存较高;工作表大小1,048,576 行乘以 16,384

       优点:样式丰富,易于阅读,便于保存

       使用场景:用户使用2007版本及其以上版本的WPSMicrosoft Office进行浏览;工作表大小>65536 行乘以256 列;一个工作簿中需要支持>4000<64000种的样式种类。

 

附:Microsoft. Office版本限制简要说明

2007/2010

工作表大小1,048,576 行乘以 16,384

单元格可以包含的字符总数32,767 个字符

工作簿中定义的单元格样式种类64,000

2003

工作表大小 65,536 行乘以 256

单元格内容长度 32,767 个字符

工作簿中定义的单元格样式种类 4,000

以上内容可到微软官网中参考:

2010版本:http://office.microsoft.com/zh-cn/excel-help/HP010342495.aspx#BMworksheetworkbook

2007版本:http://office.microsoft.com/zh-cn/excel-help/HP010073849.aspx2007

2003版本:http://office.microsoft.com/zh-cn/excel-help/HP005199291.aspx2003

 

3.2.4问题结论

1、数据存储什么样的格式便于数据分析、处理?

  答:结合上述特点决定。

2、大量数据处理非常消耗内存,如何避免内存溢出?

  答:少用对象,多用字符串,能及时释放内存。一般采用htmlxmltxtcsv格式解决。

3、当前格式的文件限制条件是什么?

  答:上述特点描述中有答案。

4、如何降低文件大小利用有限的宽带进行传输?

  答:压缩。

5、超过当前文件存储限制的大小如何处理?

  答:文件切割打包,xlsetxlsx创建多个sheet

6、该文件的第三方软件支持?

       答:如无特殊要求,尽量用业界通用的xlscsvtxt等通用格式。

3.3  工具插件的选择

       市场上有各类的报表开发工具,本文就从以下五个方面进行比较,方便大家选择理想的开发工具:基本功能、协议、支持条件、二次开发、学习成本、第三方软件支持、性能(未经过实践)、使用说明文档。

 

3.3.1 各类报表简介

 

1DynamicJasper(原名JasperReport,开源软件LicenseLGPL v2

u  支持的功能:

    运行时定义列

    自动报表布局

    支持动态交叉表

    嵌套子报表

    丰富灵活的样式

    支持图标

    支持变量

    支持多种文件格式导出(PDF, XML, HTML, CSV, XLS, RTF, TXT

    支持框架集成(Struts2WebWorkGrails

    提供专业的支持

u  支持的条件:JDK1.5及其以上。

u  学习成本:有丰富的使用群体,提供详细的使用文档,学习成本低。

u  二次开发:已提供源码,采用LGPL协议,如果要修改LGPL协议的代码,则涉及修改部分的额外代码和衍生的代码都必须采用LGPL协议。因此LGPL不适合以LGPL协议代码为基础进行二次开发的商业软件。

u  后续支持:稳定,有团体维护。

 

u  第三方软件支持

       iReport:独立的报表设计器IDEiReport是为JasperReports设计的强大的,直观的,易于使用的可视化报表设计器采用纯Java开发。这个工具允许用户可视化编辑包含charts,图片,子报表等的复杂报表。iReport 还集成了JFreeChart图表制作包。允许用户可视化地编辑XML JasperDesign文件。用于打印的数据可以通过多种方式获取包括:JDBC, TableModels, JavaBeans, XML,Hibernate(支持HQL查询语言), CSV等。它支持多种输出格式包括:PDF,RTF,XML,XLS,CSV,HTM

       JasperReports LibraryJasperReports开源库,扔到应用Lib中可以使用Report

       JasperReport Server:服务引擎(如果是自己建立的项目来实现报表展示,可不采用本服务)

       JasperReports ETL:提供数据提取、转换、加载服务,可用于BI

       JasperAssistant 3.1.1Commercial)(JasperReports的可视化报表设计器):采用Eclipse插件集成到eclipse中,可试用21天。

       SWTJasperViewer是一个专门为基于SWT/JFace应用程序与Eclipse插件开发的JasperReports报表查看组件。

       Jasper4FlexJasperReports的一个插件,它利用AdobeFlex SDKJasperReports生成的文档导成Flash格式。

 

       DynamicJasper提供了一套高级API用于隐藏Jasper Reports的复杂性。能够帮助开发人员节省设计简单或比较复杂报表所需要发费的时间。能够动态创建报表、在运行期定义字段,字段宽度,分组等。动态列报表:可以定义列在运行时,这也意味着你在运行时控制列的位置,宽度,标题,等。支持多组/分组,支持报表自动布局,支持动态交叉表,支持嵌套子报表,支持灵活的样式等等,具体可到官网了解:

       http://dynamicjasper.com/features/

 

2BIRTLicense:EPLhttp://www.eclipse.org/birt/phoenix/

       BIRT是一个Eclipse-based开放源代码报表系统。它主要是用在基于JavaJ2EEWeb应用程序上。BIRT主要由两部分组成:一个是基于Eclipse的报表设计和一个可以加到你应用服务的运行期组件。BIRT同时也提供一个图形报表制作引擎。

u   支持的功能:

    独立的客户端、Eclipse插件开发

    样式采用CSS规则解析,免去高成本学习麻烦。

    支持报表中复杂的计算规则

    支持多数据源

    GUI报表设计器,界面友好,支持拖拽。

    在统计汇总上BIRT使用有一些小问题。

    易于开发,开发效率高

    灵活的数据定制功能:支持动态参数查询、数据过滤器

    支持JavaScript

    BIRT可扩展性好

    支持复杂的报表设计

u   支持条件:

       BIRT 4.3版:开发运行需1.6 JDK/JRE

       BIRT 3.7版:开发运行需1.5 JDK/JRE

u   二次开发:提供了完整的API文档,易于开发;且有源代码支持。

u   后续支持:稳定更新升级

u   学习成本:知识点较多,学习成本稍高。有全面详细的使用文档,详细的开发指导、集成部署说明、开发样例、Birt书籍(英文版),有丰富的使用群体

u   扩展功能:Open Data Access (ODA) Extension APIReport Item Extension API 。(即支持其他数据源扩展,支持新报表类型扩展)

 

 

3OpenReports (License:GPL V2http://oreports.com/)

       OpenReports是一个功能强大、灵活、易于使用的,开放源码的,基于浏览器的Web报表解决方案,它提供了动态报表生成和灵活的报表调度能力。OpenReports支持各种开源报表引擎,包括JasperReportsJFreeReportJXLSEclipse BIRTOpenReports还包括QueryReports,和ChartReports的,不需要预定义的报表定义即可轻松地创建基于SQL的报表。此外,OpenReports现在通过MondrianOLAP服务器)和JPivotJSP 自定制的标签库,可以绘制OLAP表格和图表)支持OLAP

       WAR包部署应用服务器运行,在浏览器中可管理报表集合、用户组、浏览系统运行状态、系统配置。优点是可在本源码的基础上改造为一个新项目。本项目遵循GPL协议,建议不要商业化,可在内部使用。

u   支持的功能

    支持多种导出格式,包括PDFHTMLCSVXLSRTF,和图像。

    基于Web管理用户、组、报表、参数和数据源。

    灵活的调度,包括每小时、每日、每周、每月和的cron调度和多个收件人。

    完整的报表参数支持,包括日期、文字、列表、查询和布尔参数。

    细粒度的安全控制访问报表、调度和管理功能。

    报表审计轨迹开始时间,持续时间,状态,用户的每一个生成的报告。

    支持多个JNDI或使用连接池的数据源生成报告。

    支持向下钻取报表和外部应用程序集成。

u   缺点:GUI非常简单,不支持报表内计算,样式定义复杂(通过XML描述样式,不如可视化效率高),不支持分组,使用群体少

u   二次开发:已提供源码,licenseGPLv2,可以自由修改软件。需要注意的是任何软件使用FreeReportBuilder开发后,该软件的发布协议必须是GPL协议,即发布时需要开源自己软件的代码。

u   支持条件:JDK1.5及其以上

u   学习成本:有详细的使用文档,使用群体少。

u   技术框架:Servlet/JSPSpringHibernateStruts2

u   后续支持:支持不稳定,估计2年左右更新版本。

 

 

3.3.2 各类报表比较结果

       各类报表基本上都支持主流的功能,但各个报表都有各自的优缺点,以下内容只是提供参考建议。

1)开发复杂的报表、使用群体丰富(易于解决问题):DynamicJasperBIRT

2)开发效率高、易于维护:DynamicJasperBIRT

3)可复用已有项目或者基于已有项目进行个性化开发:OpenReports

4)易于应用集成或者有丰富的第三方软件支持:DynamicJasper

5)界面漂亮且易于维护:DynamicJasperBIRT

 

 

 

3.4  数据库层

       从数据库层考虑,包括以下几个方面:

1)数据库设计优化

       包括分表、分库等。

2SQL优化

       索引,查询语句等。

3)数据库操作优化

       减少数据访问(数据库IO

       减少交互次数(网络传输)

       返回更少数据(网络传输)

       减少服务器CPU和内存开销

       利用更多资源等。

 

       关于数据库方面的研究,网上有很多较专业的资料。本文仅仅起到一个提示、点拨的作用。遇到问题时可直接在网上搜索。

 

3.4.1 数据库设计优化

       此处只提两点经验——分库和分表。

1)分库:设计上可以按OLTPOLAP的概念来分库。

       当今的数据处理大致可以分成两大类:联机事务处理OLTPOn-Line Transaction Processing)、联机分析处理OLAPOn-Line Analytical Processing)。OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。

       像统计报表之类的服务,其定位应该是侧重于对数据的分析、决策支持,故属于OLAP的范畴。OLAP的特点是,主要是读操作,很少涉及写操作,而且理论上支持处理大量的数据,支持上百万条记录的读,但是设计上为支持较少的用户数。OLAP的数据是多维聚集的、统一的,通常是综合性和提炼性数据、历史数据,数据不可更新,但是可以周期性刷新。

       OLTP数据库,是面向实时操作的数据库,要支持日常的增删查改操作。它强调响应时间,特别是对于交易系统。

       对于OLAP系统的设计,又可以分为物理OLAP、虚拟OLAP、混合型OLAP。通常所说的数据库OLAPOLTP分离,基本上都是属于物理上的分离,也就是分库,这是见效最快的一种。考虑到实际情况、成本、复杂度等,也可以采用虚拟OLAP,即通过数据库视图(view)等形式,把那些应用频率比较高、计算工作量比较大的查询作为实视图存储起来,优先利用已经计算好的实视图来生成查询结果以提高查询效率。

 

2)分表

       通常意义上讲,分表就是把各个相关的数据分成若干个表分别存储,比如员工信息、部门信息,如果不分开,放在同一张表中,那么对于单纯的部门查询,则会比较麻烦。当然,这是通常意义上的分表操作。这一部分,在数据库设计的时候,应该兼顾效率以及易用性——并不是表分得越细越好,也不是越笼统越好。

       更高级的分表操作。针对数据量特别大的表,比如说帖子记录表,在这个表中,可能有上亿个帖子信息,如果我们不分成几个表来存储,那么对于数据的查询和更改那将是一个巨大的挑战,仅仅根据ID加上索引已经远远无法满足我们的性能要求了。所以可以采取分表存储,假设分成POST_00POST_01,……POST_99100张表,对于每条记录,都使用一套统一的ID,然后将ID根据一个算法进行处理,假设都能平均的分布到0001、……99之中去,那么根据这个结果将帖子存储在对应的表中,查询或更改的时候,也首先根据它的ID去找它是属于哪个表中,然后再在那个表中去操作。

       如图所示:

clip_image033.png

       另外,还可以根据字段去分表,比如,假设有一个表中某个字段可以取值为CATUMFSC等几个值,那么根据这个字段,可以分成TABLE_CATABLE_TUTABLE_MFTABLE_SC等几张表,根据那个字段的值选择对应的表来储存和操作。

 

 

3.4.2 SQL的优化

       非常重要的一项内容,报表处理中必须要考虑到。前面的许多地方都已经通过实例说明了:报表处理上,“数据库”是性能的瓶颈所在。单从SQL这个层面上,就可以将性能提高一大截。

       关于这方面的知识,网上有很多很多资料,有需要的应该直接从网上获取,本文就不再简单罗列了。

 

3.4.3数据库操作优化

       此节可以参考其他专业的资料,本文不做讲解。

       这里有一篇文章写得比较详细:

http://blog.csdn.net/yzsind/article/details/6059209

       本文也提到过类似的例子,可参见“本文2.3小节”。

3.5  服务器层

       对于大型报表处理系统,可以采用服务器集群,集中多台服务器的资源,成倍地提高处理速度。单纯来讲,服务器集群目前已经是比较成熟的技术,但是对于整个系统设计的细节、程序结构会有一些影响,下面将会提到。

 

3.5.1 部署方式

       设计思想:

u  同一个任务,安排多个Worker并行的去处理。

u  同一个任务,划分成多个环节,每个环节分配1NWorker去处理。

      

       大体结构分类:

       (1) 多应用服务器 + 同一套数据库( + 共用文件存储空间)

       (2)(一个应用服务器 + 一套数据库)×N

       (3) 分层、分类型部署,每一层都可以参照(1)(2)的部署方式,也可以有一个Master

 

       这几种部署方式都有用武之地,其实对于(2),服务器和数据库一一对应,相当于是部署多套系统,设计上来说要简单很多,不需要考虑数据同步、资源分配等问题。

       对于(1),多应用服务器做集群部署,使用同一套数据库便于对数据进行统一管理。而且应用服务器可以平行的扩展。

       对于(3),可以把一个系统拆分成几个模块,单独部署,例如web交互是一个模块,后端线程处理是一个模块,后端消息推送是一个模块,每个模块都可以分开部署,对于数据库,根据情况,可以使用一套,也可以另外扩展。

 

       另外,提到一点就是文件存储空间问题,因为是报表系统,必然涉及到文件的问题,对于多服务器,文件可以分开存储,也可以统一存储。对于文件的分开存储,是一个比较麻烦的事,比如,用户是随机访问一个服务器的,如何从一个服务器上去管理另一个服务器上的文件?“共享文件存储空间”能够帮我们解决这个问题,所有服务器的报表文件都放在同一个存储空间,那就不会碰到分开存储时那些麻烦事了。例如,常用的网络连接式存储(NASNetwork Attached Storage),NAS的优点是可以跨平台共享,只要把它挂载到各个服务器的目录下,就可以当做本地磁盘一样来存取数据了。

 

3.5.2 文件服务器

       对于涉及到大量报表文件存储和下载的报表处理系统,单凭应用服务器去处理这些文件操作,既影响服务器端报表处理程序的性能,又影响客户的报表下载效率。特别是对于后者,我相信很多简单的报表处理程序,都是通过HTTP方式,在服务器端通过类似于ServletServletOutputStream这种方式去推送文件。这种方式占用服务器端线程特别厉害,我曾经测试过,如果有20个这样的servlet线程同时运行,服务器(Tomcat)就变得很卡了。

       一种简单的方式是将报表文件都放到Web服务器目录下,直接访问文件地址(http://file.zollty.com/exp.xls)去下载报表文件,这种方式的效率比通过在应用服务器端用servlet去推送的方法较高,但是安全性很难保证。

       如果有这个需求,可以单独开设一个“报表文件服务器”,在这里对报表文件进行集中的管理。这个服务器的特点是,资源以“静态文件”为主,我们只需要重点关注的是权限问题——即,谁能看到报表文件,谁又能下载报表?这方面可以采用一些较轻量级的工具来做,比如NodeJSNginx

 

3.5.3 其他选择

       要想解决一个核心问题:如何能提高多线程、多进程的效率,充分利用多核CPU

       如果要设计支持海量数据处理、大量并发性操作的报表处理系统,可考虑用ErlangYawsWeb Server)来实现,Erlang是一种面向并发(Concurrency Oriented)的函数式编程语言,支持超大量级的并发线程,并且不需要操作系统具有并发机制,与其他语言相比有无可比拟的优势,它是一个完全不同的“生态系统”。特别适合核心建立在多进程、分布式、消息推送、海量数据处理等之上的应用。

       为什么是Erlang?源于一个一直困扰我们的问题——多线程、多进程不能充分发挥多核CPU利用率的问题。最原始的模式是进程之间需要锁来维持数据的完整性。而Erlang则通过消息模型,进程之间并不共享任何数据,不需要引入锁,极大地提高了多核CPU的利用率。另外其高容错率,也是一个颠覆性创新,进程可以监控和自行处理,几乎不会因为软件的错误而导致整个系统瘫痪。

 

 

参考资料


[1]  https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/

[2]  http://blog.csdn.net/yzsind/article/details/6059209

[3]  http://www.oschina.net/question/tag/erlang

[4]  http://www.oschina.net/question/tag/nodejs