集算报表:报表工具的二次革命

引入计算引擎

产品结构

润乾集算报表是一款面向程序人员用于开发固定报表的工具软件。所谓固定报表,是指事先已经确定好格式和数据运算规则、在客户端输入参数即可呈现出来的报表,这类报表的重点在于复杂的计算过程和格式,在呈现阶段一般不会太多交互动作。


这是集算报表的体系结构图,从图中可以看到,集算报表和包括润乾报表在内的其它报表工具相比,多了一个计算层:数据源的数据先经过计算层的计算,再传给呈现层去展现。
以前的报表工具都是直接由呈现层连接数据源,报表的数据准备工作要么通过SQL(存储过程)在数据源环节完成,要么使用外部自定义数据源在应用程序(Java)中完成,总之都不是报表工具的任务。
当然,集算报表仍然支持由呈现层与数据源直接连接的方式。计算层不是必须的。

为什么要增加一个计算层呢?
我们知道,“零编码开发报表”是许多报表工具厂商(包括润乾)都喊过的口号,但现实并不是这么简单,虽然确实有不少简单报表真地可以拖拖拽拽完成,但仍然有相当数量的报表必须经过编码才能完成。

有报表开发经验程序员都知道,为报表编写复杂SQL是经常的事,存储过程以及事先准备的中间数据也是家常便饭,有时SQL和存储过程不好写的运算还需要采用自定义数据源即Java程序来处理。
虽然大部分报表相对简单,复杂报表总数上并不算多,但其占用的开发工作量却会是绝对的大头,开发10个用报表工具能拖拽完成的简单报表所用时间也做不了一个需要编码才能准备好数据的报表。

润乾报表的计算能力在市场上已经算是比较强的,可以在呈现阶段直接实现多数据源关联计算和比上期同期比等层次格间引用计算,但即使这样,我们在经营报表工具过程中仍然大量碰到复杂数据准备的需求, 开发工作量很繁重。这样,引入计算中间层希望更便捷地实现数据准备就是个比较自然的想法,也就产生了集算器这个产品。面对这类报表,我们不能再期望零编码,而是基于集算器简单编码。
严格地说,数据准备虽然对报表开发很重要,但已经不属于报表工具的任务范畴了,作为报表工具厂商,也可以名正言顺地拒绝处理这个事情,把数据准备丢给数据源和应用程序。不过我们还是努力把它包括进来,这和润乾公司未来的产品战略相一致。

理解集算器


集算器是一种脚本语言,有自己的语法体系。它有对象概念,但没有继承和重载机制,严格地说不算是面向对象的程序语言,分支循环结构很简单,有BASIC这类初级程序设计水平的程序员都能很快掌握,一天左右就能上手编程。不过,其中涉及的数据对象及其上的计算方法比较多,毕竟需要实现的计算还是比较复杂,要熟练掌握可能需要数月的时间。

与一般写成文本的脚本语言不同,集算器的脚本是写成格子里的,这样能有许多好处:无需为临时变量起名,直接象EXCEL一样使用A1、B2这种单元格名;循环和分支语句的作用域用缩进直观界定,不需要使用花括号或BEGIN/END,可以收缩显示节约屏幕,单元格中长代码可以只显然部分并使用漂起的tag式注释,同一行中书写多句代码而仍然清晰,方便阅读时能掌握代码的整体结构。还有完备的调试功能,单步执行,并在过程中随时查看中间变量的计算结果。

集算器对外提供标准的JDBC接口,可以方便地嵌入Java开发的的宿主应用中,调用集算器脚本就相当于在数据库中调用存储过程,支持参数输入,其返回结果也是大家熟知的ResultSet,对于Java程序员而言,集成集算器的学习成本非常低。
另外,集算器还是解释执行的动态语言,可以在运行过程中拼出代码执行,从而获得更大的灵活性。

降低报表开发难度

我们来看引入计算层之后对于报表开发带来怎样的好处。主要有三个方面:

首先是能降低报表开发难度,也就会提高开发效率,这是集算器的设计初衷;其次是能够优化报表的应用结构,使报表模块更为独立,减少不必要的冗余动作;然后还能够提高报表的运算性能,充分发挥硬件和系统的效能。
下面将分别详细讨论。

比Java和SQL更易写

当前复杂报表的数据准备工作一般是采用Java或SQL完成的,存储过程以及中间表也可以看作是SQL。集算器的语法比Java和SQL更为简单易懂,采用集算器能在很大程度上简化这些开发量。

Java等语言没有提供批量数据计算的类库,写个简单的SUM也要好几行,更何况分组、连接等运算,而对于过滤、汇总用到的通用表达式计算,基本上是大多数应用程序员无法完成的任务了。而且Java其实也没有通行的结构化数据对象,只JDBC有个几乎没有计算能力的ResultSet。直接用Java实现报表数据准备非常繁琐。
集算器则提供了大量与结构化计算相关的基础对象和方法,分组汇总这些只要一句,而且是解析执行的动态语言,可以进行随意的表达式计算。使用集算器完成报表数据准备工作要比Java容易得多,代码也要短小很多,这一点很好理解,就不具体举例了。
代码短小不仅是写得更快,而且还能容易理解算法和排错,绝大多数报表的数据准备算法可以在一个屏幕内显示出来,可以更直观地理解代码的整体含义。而使用Java时,一个完整的业务逻辑常常需要几百行代码,翻看到后面时已经忘了前面的了。

有经验的程序员都知道,SQL用来实现很零碎的多步运算很不方便,特别是与次序相关的运算,程序员常常要把数据从数据库中取出来用Java等完成。而集算器则正好在这方面做了强化,在分步计算、集合化、有序计算和对象引用等几方面做了完善,对于常用的日期和字串等运算,也比大部分SQL提供了更丰富的方法。

许多情况用SQL也不是写不出来,但不能直接按自然思维实现,很费脑筋,这种代码放时间长了程序员自己都会忘了是怎么写出来的,给将来的维护也造成麻烦。集算器代码则符合自然思维习惯,即使是与SQL相同的思路也能更清晰地表达,更容易理解和维护。
集算器简化SQL编码的例子很多,我们在另一个片子罗列了一些具体问题,有兴趣可以去参考。
不过,需要说明的是,集算器虽然在大多数情况下的语法要比SQL更简单易写,但并不能完全取代SQL。数据从数据库读出的IO成本相当高,有些涉及数据量太大的简单运算,数据读出的耗时远远超过运算本身,这种情况还是放在数据库中运算更合适。

比报表中计算更广泛

报表工具都可以完成计算列、分组排序等运算,润乾报表还提供了跨行组运算和相对格与集合的引用方案,可以完成相当复杂的运算。
报表工具中的运算是一种状态式的计算,也就是把所有计算表达式写在报表布局上,由报表工具根据依赖关系决定计算次序。这种方法好处是很直观,在依赖关系不太复杂时能一目了然地了解各单元格的运算目标。
但是,在依赖关系较为复杂,数据准备计算需要分成多步时,状态式计算就困难了,要实施过程式计算,经常需要借用隐藏格,隐藏格不仅将破坏状态式运算的直观性,由于状态式计算一般需要全内存处理依赖关系,还会占用更多不必要的内存。而且还有许多运算即使用隐藏格也难以完成。

比如要列出销售额占前一半的大客户,如果不借助数据准备环节,就要在报表中使用隐藏行列手段将不该列出来的条目隐藏,而不能直接过滤掉。再比如带明细的分组报表要按汇总值排序,需要先分组后排序,许多报表工具无法控制这个次序,报表就无法完成了。
还有个典型例子是舍位平衡,明细值四舍五入后再合计,可能会与合计值的四舍五入值不相等,会造成了报表上明细与合计数值不一致,这时需要根据合计的舍入值倒推明细的舍入值,这不是报表工具能搞定的事了。
这几个运算的逻辑都很简单,但在报表工具中却很难实现,单句的SQL也很难写,而为了这种简单且无复用价值的运算写一段Java程序或存储过程显得很无聊,况且也不是很轻松。但是,如果采用集算器就会容易得多,在数据准备阶段完成计算,报表只负责呈现及少量的直观计算,能有效地保持状态式计算的优势。过程多了一步,但结构更为清晰。

数据准备还能实现动态数据源和数据集。报表工具使用的数据源一般事先配置好了,不能根据参数动态选择,而使用集算器数据源时则可以用脚本控制连接不同的数据源。通用查询报表要求取数SQL不能简单地用参数控制条件,而要替换某个子句,支持宏的报表工具能够一定程度地解决这个问题,但面对要将宏计算后才能拼进SQL的复杂场景也会感觉困难,传统方案一般又要在外部编写Java程序事先将宏计算好再传入,增加开发工作量。而采用集算器则可以轻松使用脚本直接完成各种复杂宏计算了,而且,由于集算器与集算报表的深度集成方案,还可以将脚本计算后的宏值回传给报表,在单元格中继续使用,获得更为灵活的报表模板复用效果,进一步减少开发工作量。

数据准备不仅能解决报表中的计算,还能协助处理格式。比如许多报表工具都支持纵向分栏,但很少有报表工具支持横向分栏。而用集算器可以将原数据集变换成横向拼接过的数据集,报表工作只要用普通的模板呈现列数更多的数据集即可。再比如许多报表工具不支持末页补足空行,也可以用集算器在返回数据集时补。

一致的多样性数据源支持

现代报表的数据源并不只是数据库,还可能是文本文件或json、XML等。这些非数据库数据源没有再计算能力,但出报表时总还是需要再进行一些过滤分组甚至多表连接等运算,报表工具本身的计算能力不足,一般都不能很好地处理json和XML数据,即使针对能进行简单处理的结构化文本,由报表工具运算也也会造成容量负担过重的问题。因此,我们经常会有一个过程把这些非数据库数据导入到数据库再去生成报表,增加开发工作量。
如果采用集算器准备数据,则可以直接用这些非数据库数据作为数据源去生成报表,不需要导入数据库的过程,减少开发工作量。

数据库还包括NoSQL数据库、文件包括HDFS文件,对于集算器来讲都是数据源。集算器自有的计算能力可以使这些计算能力不一的多样性数据获得通用一致的计算能力。比如文件几乎没有计算能力,MongoDB对JOIN和GROUP运算支持不足,各家数据库对窗口函数的支持程度不同、日期与字串处理能力也普遍不足且风格迥异。采用集算器后可以用相对一致的方案来计算,而这将意味着更低的移植成本以及学习难度。

一般报表工具使用的数据集都是类似SQL返回的那种单层二维表,碰到象json或XML这类多层数据只能先转换成多个单层数据集,再在报表模板中关联运算拼接成多层报表。而集算器可以直接支持多层数据集计算,不需要做这个转换,减少工作量,集算报表也可以接受集算器返回的多层数据集直接按层次呈现,不需要在报表中再做关联。
类似地,MongoDB也支持多层数据,也可以用集算器直接计算并返回给集算报表。

优化报表应用结构

提高开发效率是集算器的设计初衷,但实际应用下来,优化报表业务的应用结构才是集算器能起到的重要作用。可以说,集算器的出现是报表工具的二次革命。

解释执行降低应用耦合度

我们再来比较使用Java和用集算器做报表数据准备时在应用结构上的差异。

我们知道,Java应用大多数情况是事先编译并静态加载的,也就是要把所有模块一起编译打包后部署,在运行过程中代码就不再改变了。其实Java也有动态编译和加载的技术,但难度和复杂度都高很多,一般应用程序员很少使用。而且即使采用了动态加载技术,也不能替换已经加载进内存的类,只能不断新增新的类。
这种机制下,用Java编写的报表数据准备算法就需要和主应用程序一起打包发布,这会导致报表模块与主应用之间的高耦合性。
一般来讲,报表的业务稳定性要比主应用差得多,报表变动的频率远远高于主应用,好的体系设计应当能把报表模板独立出来降低整个应用的耦合性。但是,一个完整的报表由报表工具开发的报表呈现模板和Java开发的数据准备算法构成,报表修改时需要同时改变这两部分,报表模块和主应用的耦合度就会很高。因为报表模板一般保存在文件系统中或有时在数据库中,而数据准备算法却要打入了主应用程序包,存放和管理机制都不一样,这样要保持两者一致就相当麻烦了,报表模块很难独立出来。
而且,如上所述,Java大多是静态加载的,报表数据准备算法有修改后会导致整个应用重新编译部署,很难做到热切换。

如果用集算器来实现数据准备算法,就能有效地降低主应用程序与报表功能的耦合度了。
集算器写出来的脚本也是类似报表模板的外置文件,不需要和主应用程序一起编译打包,而可以和报表模板一起放在文件系统中管理维护,报表模块可以独立出来。
集算器是解释执行的动态语言,在修改时不需要涉及主应用程序,只要把集算器脚本替换就可以,天然就支持热切换。
另外,Java编写的算法一旦加载后就会占据内存不再释放,即使相应报表不再被访问。而使用集算器没有这个问题,算法执行完后会立即释放,不再占用内存。

算法外置减少存储过程

在体系结构方面用存储过程准备报表数据和用Java程序是类似的,也会造成耦合度高的问题,只是从报表模块与主程序之间的耦合变成的报表模块与数据库之间的耦合。
存储过程存放在数据库中,报表模板放在文件系统中,保持两者同步修改依然很麻烦。
存储过程修改时需要申请一定级别的管理员权限做重编译,虽然不象Java那样难以做到热切换,但数据库高权限的频繁使用又会带来安全隐患。
比Java更糟糕的是,数据库及其中的存储过程可能被多个应用共享,如果管理不善,很容易造成多个应用之间的高耦合,时间长了会搞不清楚某个存储过程在被哪些应用调用,越来越混乱。
同样地,采用集算器也可以极大程度地减少数据库中的存储过程,算法外置后与报表模板一起存放管理,完全归属于报表模块,不仅降低与应用其它部分的耦合,更不会造成与其它应用的耦合。

实际上,存储过程本身编写难度并不小,遍历式计算代码的性能也不佳,而且可移植性很差,原则上在报表业务中应当尽量少用存储过程。
不过,集算器并不能完全替代存储过程,某些涉及数据量巨大难以移出库外计算的情况还是可能要用存储过程做些预先处理。

数据外置减少中间表

数据量巨大或计算过程太复杂时,我们经常会事先对原始数据做些处理后形成中间结果,再基于这些中间结果开发报表。这些中间结果一般是以数据库表的形式存在的,也就是这里所谓的中间表。
一个运行时间较长的系统中,中间表的数量往往会远远大于原始表。某些移动公司的数据库中有上万个表,即使很复杂的业务用五百个表也基本能描述了,这些上万的表中绝大多数都是为报表服务的中间表,这肯定是数据库厂商都没想到过的情况。

与存储过程类似,大量的中间表也会造成数据库管理的混乱。
数据库中的表是以线状方式存储的,相当于没有分类,而数据库被各个应用共享,中间表都混到一起,很难搞清楚。这需要项目组有很强的管理控制能力才能理清,比如规定中间表的命名规则并保证得到执行,但强化管理常常是以牺牲开发效率为代价的,项目时间一紧张就顾不得这些规矩了。
管理能力不够好其实是常态,这就会导致中间表越来越多,积累到上万可能是有点极端,但总归不是个小数目。这些中间表可能有相当多已经没有用了,但因为不清楚有哪些应用还在使用而只能先留着,相应的ETL过程也仍然要无意义地浪费计算资源继续更新数据。

那么为什么要把这些中间结果存到数据库中,而不能存放到文件系统中呢?
这是因为我们不可能为每个报表的每种参数组合事先计算中间结果,在生成报表时还需要根据参数进行计算,也就是要求这些中间结果仍有计算能力,而目前只有数据库有这种计算能力,文件是没有计算能力的,于是中间数据就只能变成中间表。

中间数据一般都是由不再改变的历史数据计算出来的,完全不需要数据库的事务一致性能力,因为都是导出的冗余数据,也不需要很高的稳定要求,数据坏了重算一次就行了,存放在数据库中仅仅为了获得计算能力,实在是划不来的。
有了集算器后,就可以将中间数据外置到文件系统中,由集算器提供针对文件的计算能力,可以有效地减少中间表,不必再让这些冗余数据继续占用昂贵并且低效的数据库空间。
外置的中间数据文件还可以使用文件系统的树形结构管理,与报表模板及数据准备算法统一存储。这不仅管理简单方便,而且,由于不考虑写入和一致性的需求,文件还会比数据库有更好的IO吞吐性能,整体提高报表的运算速度。

混合运算实现T+0报表

关系数据库的事务一致性能力目前尚没有有效的替代者,交易系统仍然有必要使用关系数据库来建设。
这种情况下,要实现T+0全数据量的实时报表,我们就得把历史数据继续存放在当期的交易数据库中一起计算,历史数据常常要庞大得多,这会要求我们建设更大容量的数据库,成本当然会很高。而且即使愿意支付成本,这个数据量也不可能一直增长,太大了会影响到交易业务的性能,这就不可容忍了。

通常的办法是把部分历史数据被移出来做个分数据库,这样可以保证交易系统的正常运转,但要实现T+0报表就麻烦得多,会涉及到跨库运算。
许多数据库都支持跨库运算,但一般都要求同类型的数据库,但历史数据和当期交易数据的要求不同,数据量更大但不要求事务一致性,很可能使用另一种数据仓库来存储。
而且,即使是同构的数据库,数据库的跨库运算的方法一般也是将另一个库中的数据表映射成本库数据表,实际运算还是一个数据库在做,而且还多出许多数据传递的通讯成本,性能和稳定性都不好。

使用集算器就可以很好地完成这个混合计算任务了。
集算器自己有计算引擎,不依赖于数据库,各个数据库内的数据计算仍由各库进行。集算器可以使用多线程向各数据库同时发出SQL语句,由这些数据库并行执行,将各自的运算结果返回到集算器再汇总处理后传给报表工具去呈现。
显然,这种机制还方便横向扩展,历史库可以有多个,是否同类型的也无所谓。
而且,集算器还有服务器方式的集群运行模式,在集算服务器的支持下,历史数据不必一定存放到数据库中,还可以存储在IO性能更好的文件系统中,配合集群计算,可以在更低的成本下获得更好的性能。

直接使用多样性数据源

象前面提到过的,集算器可以计算非关系型数据库和文件数据。直接使用多样性数据源制作报表,这不仅减少了将数据导入关系数据库的开发工作量,而且在应用体系上也更为简单,没必要为了获得更强的计算能力增加多余的关系数据库,成本降低还减少了数据导入过程中导致的不一致风险。
非关系数据库在某些方面比关系数据库更强,只是计算能力不足或不同,用集算器辅助计算后可以保留其原有的优势。比如MongoDB的对追加型日志数据的吞吐能力就远远超过普通关系数据库,但结构化计算能力较弱,用集算器来弥补后,数据可以继续留在MongoDB中,即获得其高吞吐性能也有了结构化计算能力。

提升报表运算性能

从后端向前端看,报表的性能问题主要会发生在这么三个环节。
首先是数据源,大多数涉及数据量较大的报表,性能瓶颈几乎都是数据源造成的,常见的表现就是发给数据库的SQL执行速度不够快;其次是取数环节,常见数据库中,Oracle和MySQL的JDBC性能很差,SQL执行得快,但数据取到报表工具却要很长时间;第三是报表本身的运算,有些关联或分组计算在报表工具中实现时会很慢。
碰到性能问题时,要根据报表生成的日志研究确定是哪个环节出的问题,然后才能对症下药地进行优化,当然也可能三个环节都有问题。
集算器对这几个环节都有一定程度的优化作用。

集算器替代报表计算

多源关联报表是很常见的形式,其具体形式是将多个数据集按某个关联字段,这在SQL中是典型的JOIN运算。然而,在报表模板中,对齐关系是用单元格内表达式各自定义的,无法描述数据集之间的整体关联关系,这样在运算时实际上是在遍历对齐,复杂度是平方级的。一般情况下这类表只有几十行,计算量虽大也感觉不到慢,但如果报表有数千行时,对齐的计算量就会有上千万次,性能问题就会很明显了。
用集算器则可以事先将多个数据集对齐后再提交给报表工具作为单数据集呈现,和SQL一样,集算器也使用了高效的HASH算法实现JOIN,复杂度是线性级的,对于上述有数千行的关联表性能也能提高数千倍。

类似地,集算器对于分组运算也采用了HASH方式,比一般报表工具用的排序方案要快得多。还有,前面提到过的直接使用关联好的多层数据,在减少开发工作量的同时,也可以减少报表环节的关联计算量,而获得更好的性能。

报表工具采用的状态式计算方案要么无法利用中间结果,每次计算都只能从原始数据和单元格开始,有些涉及单元格集合的运算效率会很低;要么就采用隐藏格来保持中间结果,这又会占用太多内存,而Java程序的性能对内存非常敏感。报表计算时是带着单元格外观属性一起进行的,这会占用更多内存。
使用集算器事先准备数据则没有这些问题,过程式计算可以方便地复用中间结果,只进行纯粹的数据计算,不涉及隐藏格和外观属性,内存使用效率更高。

使用缓存能够有效地改善报表响应的用户体验,高端的报表工具一般都提供缓存功能。但报表工具的缓存机制比较死板,只能针对整个报表,不能只缓存报表的某个部分,两个报表有共同部分也无法复用缓存,也没法分别指定不同报表缓存在不同参数下的不同生存周期。因为报表工具采用可视化的配置方案,虽然使用简单,但很难设置过多复杂的参数。
用集算器准备数据时就可以实现可控缓存的效果,集算器是程序代码,可以由开发者灵活决定使用缓存的时刻及范围的策略,这样就可以实现报表的部分缓存、多个报表之间缓存复用、以及不同缓存的不同生存周期。

对于高并发报表,我们还可以利用集算器的内存共享机制,将报表用到的数据缓存在内存中,几个报表可以共享这份内存中数据。从内存中计算不仅能获得数倍于数据库或文件的访问性能,而且可以更方便地实施并行计算,充分利用现代CPU多核的优势,而集算器编写多线性并行计算代码也是非常简单的。
对于这种场景,集算器还提供了内存字节表的方式来提高Java内存利用率,数据加载成字节,在使用时才对象化。牺牲大约30%的性能,但可以将Java的内存利用率提高3至5倍,使用内存后,即使牺牲掉30%的性能也仍然远远比数据库和文件等外存更快。

集算器优化数据源计算

原则上讲,数据库本身的性能无法被报表工具以及其它数据库外部的技术手段优化,只能想办法优化SQL的写法。不过,还是有些场景可以使用集算器提高数据库相关的性能。
大多数情况下SQL的执行效率都较高,但如果SQL过于复杂,有许多子查询再与JOIN和GROUP等嵌套在一起时,数据库可能会表现出很糟糕的性能,原因是数据库不能正确地优化复杂SQL的执行路径,而SQL不提倡分步的语法让程序员实施干预也相当困难。
我们在实际应用过程中就碰到过几例,其中有一例是这样的:要针对若干子查询的结果再做JOIN+WHERE+GROUP,SQL执行的非常慢,接近7分钟。但把每个子查询单独执行并不慢,总和不到1分钟。于是我们用集算器作为主控程序,分别执行每一个子查询+WHERE,把结果集取出后在集算器中实现后续的JOIN+GROUP,结果性能从近7分钟降到了1分多一点,提高了5倍。
具体原因只能猜测了,估计是数据库在优化这句SQL时,把子查询拆开了和JOIN一起做,反而性能下降。如果有对数据库优化很精通的程序员,也许也能通过调整某些优化参数让数据库找到正确的优化路径,但这毕竟对人员要求过高了。而使用集算器就可以自由控制执行路径,部分运算移出数据库实施,总体上起到性能优化的效果。

前面曾提到过,Oracle等数据库的JDBC性能较差,而报表性能又严重依赖于取数环节,在数据库负担不重的时候,SQL可能飞快地执行完,但取数却需要好几分钟,让用户无法忍受。这时候可以可以采用多线程并行的方式同时建立多个数据库连接从数据库分段取数,我们实际测试表明并行取数的性能提升基本上就是在做除法。但是,报表工具无法直接实现这个效果,而借助集算器就可以轻松实现。

还有些数据源计算是用Java编写的。理论上讲,集算器是用Java开发并解释执行的,多过了一道手,不可能比直接写Java代码有更好的性能!但在实际情况中,大多数应用程序员更擅长处理业务计算,而不是很精通编写这些底层算法。举个例子,很多应用程序员不会写HASH GROUP算法,或者是懒得写,代码毕竟复杂了太多,结果就用排序做GROUP,这时候Java直接编码的计算效率就会远远低于使用集算器了,因为集算器已经实现了许多高效算法可以直接使用。

报表的数据源有时是事先准备好的,就是前面说过的中间表。而计算这些中间表也是用SQL、存储过程或者Java来完成的,刚才这些分析对这类工作也是成立的,采用集算器经常也能提高中间数据准备的性能,减少ETL的窗口时间。而且还可以准备成库外文件,在报表生成时获得更好的IO性能的同时减少数据库负担,进一步优化整个报表应用的综合性能。

集算器协助大数据报表

集算器的多线程机制还可以用于操纵多个数据库以集群方式并行计算以获得更高性能,这个优势还可以横向扩展。

前面说过的T+0报表即是采用多数据库集群方案。多个数据库分段存储数据,每个数据库的数据量都不太大,保证运算性能够好。集算器以并行方式发出SQL给各个数据库分别计算,收到结果集后在集算器再汇总后提交给报表工具呈现。常见的过滤、分组汇总等运算都可以很方便地完成。数据量进一步增长时可以再增加更多的数据库分段以实现横向扩展。
使用数据库本身的集群方式,配置复杂度还是环境成本要求都远远高于这个方案。采用集算器实现多数据库集群还允许异构数据库集群,比如可以将小型机上的Oracle与PC服务器上的MySQL集群起来。

我们知道,文件系统的IO性能要远远好于数据库,原因可能是数据库要为写操作预留空隙,存储不够紧致了,而且为保证读一致性要更多地扫描回滚段也会消耗时间。不管怎么说,数据库的IO性能远低于文件是个不争的事实。
如果能够把数据外置出来,采用集算器的压缩数据格式存储,则可以获得比数据库更高的性能。实际测试表明,集算器对大数据的遍历性能在单线程时与Oracle基本相当,如果使用多线程并行时则会有明显优势,在数据量超过内存数倍时,Oracle的并行选项基本不起作用,而集算器并行时对性能的提升基本上和做除法一样。
集算器还支持列式存储,对于访问列数较少的查询和报表比行式数据库能有数量级的性能提升,而且这个列式存储也支持分段并行。
在脚本执行方面的测试,集算器代码解释执行性能也极大幅度地优于Oracle存储过程代码。这样,对于难以直接使用SQL写出的过程性复杂运算,如果将数据外置使用集算器运算,也将好于在库内使用存储过程运算。
前面提过,如果内存较大,还可以事先将数据读入内存获取更高性能。集算器支持内存记录的引用,用这种方式表达的连接运算与传统SQL的外键对应方式相比,不仅运算描述更为简单,运算性能也高得很多。实际测试表明,集算器的指针连接方式能比同容量的Oracle外键连接方式快出一倍以上。
另外,集算器本身也有可集群的服务器,在数据量更大时还可以使用集群进一步提速。
关于集算器在大数据计算方面提供的支持,我们另有一个片子进行更详细的介绍和代码演示,这里只是粗略地提一下。

集算报表设计理念

我们已经详细解释了作为润乾报表新版本的集算报表所增加的关键内容以及给用户带来的核心价值,下面再谈一谈集算报表的设计理念,这些能够帮助用户更清晰地理解产品的定位,了解它的适应场景。

中间件定位

集算报表定位面向开发人员的中间件类产品,必须由程序员集成到应用解决方案中,而不是面向终端用户直接可用的应用系统。
在这个定位下,集算报表将特别强调可集成性。集算报表的jar包可以与Java主应用程序一起打包发布,开放API被直接调用以获得高性能,这可以说是完全无缝集成。终端用户一点也看不出应用解决方案中集成了集算报表。
业内有些其它报表产品则采用独立服务器提供Web服务的集成方式,性能和可控性都差很多,集成性不好。

对应地,集算报表现在没有、将来也不会提供类似用户组织机构、管理看板、KPI分析这些外围管理功能,我们认为这些东西和用户行业特色密切相关,缺乏行业经验时不可能做到足够好的适应性,在管理模式套不上时,开发人员想摆脱它重新定制反而会感觉很麻烦,还不如根本没有,把控制权都交给应用开发人员。
当然,有外围应用功能的系统也有好处,毕竟容易上手,能很快搭出可用环境,对于要求不是很精致但要快速上线的用户也是有意义的。而中间件虽然可以被精细地控制,但总需有开发人员再包一层才能实际使用。
所以,这只是个适合自己的定位,谈不上好与不好。由于润乾是个缺乏行业经验的工具软件厂商,走中间件路线是适合的。

移动端

移动应用也是近年来的热点,有许多报表厂商也发布了移动端APP。
但是,作为中间件产品,集算报表没有也不能事先提供移动端APP。APP是完整应用的一环,会涉及用户登录、权限控制、资源组织等各种与场景密切相关的功能,这属于上面所说的外围管理功能,事先做好就意味着做死,不适配的可能性相当大,而已经做好的APP又没有什么可集成性,难以再编程。
虽然不能直接提供APP,集算报表仍然为移动应用做了许多准备工作。在移动APP开发中使用HTML5作为呈现手段是很常见的方案,支持HTML5的浏览器控件也是手机操作系统的基本标配。集算报表可以输出标准HTML5格式的页面,所有统计图都有SVG格式,支持纯JavaScript图形绘制,并且为移动端增加了HTML5本身不支持的长按事件。这些支持确保程序员能轻松地将集算报表嵌入到自己开发的APP中。

可视化

数据可视化也是近年较热门的话题。广义地讲,报表也是数据可视化的一种形式,但我们一般所说的可视化一般是指将数据以图形方式呈现,特别是用动画效果呈现数据的特征。
图形动画本身是个较专业的领域,涉及种类非常多。不象报表能够设计出相对统一的模型,目前的图形还只能逐个实现,工作量非常巨大。好在近年来出现了许多功能强大的图形包,能让开发者以较少的工作量即获得很好的可视化效果,这些图形包做得相当专业,而且一般都开源免费,也非常鼓励别人集成,这种情况下实在没什么必要自己再搞一遍了。
集算报表开放了接口可以很方便集成第三方JS图形包,包括如D3、百度eCharts等。程序员可以将自己熟悉的开源图形包嵌入到集算报表的单元格中,将报表单元格的数据传送给图形包,实现丰富的可视化效果,包括各种动画效果以及交互式的地图。
在这个基础上,我们也在不断对这些常见的图形包进行封装,让不熟悉这些开源图形包的程序员也能通过简单配置获得可视化效果。
不过,还要再强调的是,集算报表是个中间件产品,它依赖于这些第三方图形包提供了足够的可视化能力,但自己并没有也不可能事先做好用户希望的美观图形样式,还是需要程序员和美工人员利用这些支持进行开发和配置。

WEB制表

WEB制表是个经常被提起的话题,也有些报表厂商将此功能作为一个卖点,但集算报表目前没有也不打算提供这种功能。
集算报表的目标面向程序员开发固定报表,过程中需要大量输入表达式并且预览调试。由于JavaScript和HTML的限制,在WEB上的用户体验远远不如客户端流畅易用,可以想象一下把Java开发工具eclipse搬到WEB上会是什么感受。
但是,话说回来,WEB制表的需求并非没有道理。对于报表开发人员,只需要了解数据结构和报表逻辑关系就可以了,不想也不必关心数据库环境以及报表模板的存放规则,这些涉及到用户管理和访问权限的事情是应用设计和系统管理人员的任务,配置改动时也没必要让报表开发人员都知道。如果提供了WEB制表,报表开发人员就可以不必关心这些环境问题,单纯做报表就行了。
对于这种需求,集算报表开放了环境加载和编辑结果上载的接口。报表开发人员仍然使用客户端开发,但可以连接到远程的服务器加载相关环境信息,并可将开发好的报表及数据准备脚本上传到指定服务程序,再用这些服务程序根据用户权限执行存放规则。这样,既保留了客户端的流畅性,又可以让开发人员不再关心环境配置。

这里说的WEB制表仍然是面向开发人员的,而不是面向终端业务人员的自助制表,两者不可混淆。业务人员一般没有安装和使用报表开发界面的技术能力,当然只能直接用WEB制表,但同时,业务人员也不会开发有复杂计算过程和关联的固定报表,只能实现计算和格式都较为规整的自助报表。
集算报表不是用于解决自助报表问题的产品,润乾另有一款超维报表专门用于解决自助报表的需求,它提供了面向业务人员的完善WEB制表能力,有兴趣可以再了解。

大数据及性能

相比可视化那些,大数据是个更热的词了,现在不提大数据都不好意思说自己是做IT的,特别是报表工具还是专门处理数据的,更是离不开大数据。
但是,仔细分析一下会发现,报表和大数据在技术上并没有多少直接关系。人的视力不可能查看太“大”的数据,报表工具所谓的“大”数据,是指报表涉及的数据源很大,但到了报表呈现环节时已经被汇总过滤成小数据了。大数据造成的性能问题也是在数据源阶段解决掉,而不是报表呈现环节处理的事情。
市场上大多数报表工具都只有呈现功能而没有数据源处理能力,有的甚至连与非关系型数据库连接的能力都没有,这时宣称支持大数据并且能达到某种性能指标的,就是钻这个空子了,宣称的结果可能是对的,但和这些报表工具并没多大关系。
不过,对于集算报表,大数据处理能力是实实在在的,当然这也不是呈现环节的功能,而是其中集成的集算器做的。集算器可以用多线程并行方式实施数据库集群、可以连接并计算NoSQL数据库以及Hadoop数据、提供可分段并行的压缩及列存数据格式、还可以配合集算服务器实施集群计算。这些手段都可以有效地提高数据源处理性能。

BI与OLAP

OLAP是报表工具应用过程中经常碰到的需求,有些用户也直接称这种功能为BI。
OLAP的目标是主题分析,针对做好的数据集做旋转、切片等交互式变换。OLAP的呈现结果大多也是报表,但都是些格式和计算规律性很强的交叉分组报表。OLAP产品的擅长点在于交互,而不是复杂的过程式计算和有关联格式。
这和集算报表的目标定位不同,集算报表的重点是做复杂的固定报表,虽然可以使用链接功能实现有钻取效果的关联报表,但并没有直接提供自动的交互功能,需要程序员进行配置,关联效果也相对死板。
类似OLAP的交互功能是面向业务用户的,我们也是做到了超维报表产品中。
传统OLAP的实际应用效果并不算好,主要原因在于需要事先建设好数据集,而用户的需求经常变化,需要求技术部门不断地改造数据集,也就是建模。而超维报表采用了新的关联机制,可以让业务用户自行建模,大幅度减少对技术部门的依赖。有兴趣的用户可以仔细再了解。

技术发展方向

十年前润乾提出了非线性报表模型,比较彻底地解决了复杂报表在呈现环节的布局和计算问题,目前国内较有影响力的报表工具都采用了这个模型。经过十年的发展,报表呈现环节的技术已趋近于成熟,只有些细微改进,缺少较大的新需求刺激。报表厂商就面临着继续向哪个方向发展技术的问题。
大多数报表工具厂商采取了“向前走”的策略,即基于报表工具,加上用户权限、资源组织、Dashboard、调度等功能,逐步偏离中间件定位,加入行业经验,形成更贴近业务用户的完整应用系统。
润乾则不同,我们采取“向后走”的策略,在解决了报表呈现问题后,继续坚持中间件定位,向后台的数据计算发展,解决报表数据准备的难度和性能问题。进一步发展将不会限于报表领域,走向更广泛的数据计算和存储,逐步形成有自主知识产权的数据仓库产品。润乾从单纯的报表工具厂商转变成提供包括数据呈现在内的数据处理计算软件的厂商。
而报表呈现的功能,由于已经趋于成熟,简单地说已经缺乏技术门槛了。我们的策略是逐步降低价格,可能在合适的时候将其开源出去由业界共同做细节完善。

“向前走”和“向后走”,也谈不上正确和错误,只有适合与不适合。润乾靠创新技术赢得市场,会把竞争优势放在技术难度较大的事情上,而不是更贴近用户的细致需求,这部分问题由合作伙伴来解决会比我们自己做得更好。我们将确保产品的技术门槛,具体到报表工具,在呈现环节的技术门槛已被打破后,要在数据计算环节树立新的技术门槛,竞争者需要相当长的时间才能复制出这些能力。

填报模块

填写模块独立

老用户都知道,在润乾报表有很强大的填报功能。报表不仅可以呈现数据,还可以填写用于数据采集。但这次升版过程中,我们把填报功能剥离了。
表面上看,报表和填报功能具有很多相通性,都是表格,而且用于填写的表首先是个报表,它也有数据来源。但是在实际应用过程中,我们逐步发现,这两个功能的技术侧重点还是有很大不同。
可填写的表格需要增加不少与之相关的属性,比如编辑风格、合法校验表达式等,这些属性对于只查看的报表没有用处;可填表在格式和关联计算的需求相对简单,而统计报表在这方面的要求就复杂得多,比如分页及页内计算对填表并不重要但对报表是必须的;这两样功能混在一起时,会造成实现技术的耦合度过高,解决报表问题时还要考虑对填报可能的影响,确保不产生逻辑矛盾,这给产品完善和维护增加了不少的麻烦,一不留神就会出现顾此失彼的现象。
因此,在发展新版本时,我们把填报功能拆出来形成一个独立的模块,虽然仍然放到集算报表的整体产品中,但其处理引擎已经和报表无关了,是另写的一套。这样,两个功能可以各自专心的解决相应的问题。
类似地,其实润乾报表老版本中也有语义层和自助报表功能,在这次升版过程中也被剥离后重构了,现在形成了独立的超维报表产品。这样,整个产品线的结构和其中产品的定位更为清晰。

集算器协助填报

经过多年积累,表格填写过程中的编辑风格、合法性校验以及多页填写计算等功能都已经比较成熟,各种产品支持得都不错,新填报模块在这方面当然也继承了老版本的功能,这里不再细说。
需要关注的环节是数据处理,填报表虽然名称为填报,但其关键的技术难点却不在填,而在于数据的处理,特别是填写后的处理。


这是新填报模块的结构图,可以看出,这里也引入了集算器。

对于填报表的数据来源和去向要求,我们曾经用这样几个词来描述过:有来有去,来去无关,一来多去。填报表首先是个报表,在填写之前它并不是空的,已经有一些数据了,所以是“有来”,采集后的数据当然也要存储,也就是“有去”;存储地点和来源可以相同也可以不同,所以要“来去无关”;还可能同时存入多个目标地点,也就有“一来多去”。
这个说法,现在也仍然是有效的。

“来”的问题比较常规,报表模型已经解决得很好。对于“去”的问题,润乾报表老版本为单元格设计了一个更新属性,在其中设置填写结果将写入数据库中哪些表的哪些字段。
这个设计有两个缺点。
由于不同单元格对应的写入记录数量不同,比如交叉表中每一格可能都会对应一条记录,更新属性要散落在各个单元格中,这样不方便一目了然地看出整个表格的数据去向。一个更新属性还可能涉及到多个单元格,设置起来也比较麻烦,被引用单元格删除时有可能忘记修改相应的更新属性。
更新属性的处理方案也比较死板,只考虑了关系数据库的情况,如果数据去向不是关系数据库,就只能自己用Java编写自定义结果处理代码了;即使去向是关系数据库,如果希望在入库之前做些处理,也要用这个自定义接口来做,开发工作量较大。而且,由于数据在传入自定义接口时已经按关系数据库的习惯被解析成多条记录,导致这个接口较为复杂难以掌握,实际情况中很少用户会使用。


新填报模块把表格数据解析成几个数据集,这从表格样式上就很直观地能看出来,表格的数据来源和去向在呈现填写阶段是一致的,很容易理解表格的业务意义,不会再有原来更新属性到处散落及引用单元格的麻烦。
有来有去、来去无关、一来多去这些要求,则由前处理和后处理的集算器脚本来实现。前处理解决来源,后处理解决去向,前处理拼出的数据集和后处理接收的数据集是一样的结构,这样就确保了呈现填写阶段中来源和去向的一致性。前后处理分别是两段脚本,当然可以轻松实现来去无关,后处理脚本自由编写,写入多个去向也是很容易的事。
更重要的是,后处理脚本是针对解析后的数据集工作,接口非常清晰简单。想实现特殊的后处理也很容易,写入去向是文件或非关系型数据库都不难实现。
我们事先写好一些常用的前后处理脚本,可以直接调用,大部分简单的填报动作就不需要再写脚本了,有个别特殊情况再自行用集算器处理,也比Java代码要轻松得多。
经常被提到的多级汇总填报,在这里也就是前后处理的脚本略有不同,只要允许涉及的数据源是远程的就可以了,前处理使用远程数据源,可以在上级机构将下级已填的数据取出来汇总;后处理使用远程数据源,则可以在下级机构将填好的数据上报给上级。集算器已提供了基于HTTP协议的远程数据上下载函数,很容易实现这些功能。

业务人员自主填报

填报表的一个重要用途是采集补录数据,这个事务有很强的临时性,而且经常是由业务部门主导的,也就是由不熟悉技术的业务人员来绘制表格下发下去填写,过程中还经常修改表格式样。
采集上来的数据要用于统计分析,而只有被良好结构化的数据才能方便地进行下一步计算统计。这样就产生一个矛盾,业务人员不熟悉数据库,不知道怎么设计结构化的数据表,更不会对着数据库执行建表命令。于是只能依赖技术部门事先建立好数据结构再实施填报,这样数据采集的临时性要求很难得到满足。
润乾老版本的填报功能就是这样,没有考虑业务人员画表并结构化的需求,虽然有较灵活的数据映射机制,但没有降低数据结构化的技术门槛,仍然需要程序员才能描述表格数据与结构化存储的对应关系。当前市场上其它填报产品基本上也都是这样。
还有个别数据采集产品就是简单地按表格的行、列、格这样三字段的结构采集上来,这样虽然就可以由业务人员自己画表下发填写,但采集上来的数据相当于没有结构化,只能做简单地查看,难以做灵活的统计分析,没多大意义。


新填报模块在很大程度上解决了这一问题。对于熟悉Excel的业务人员,只要经过简单的案例讲解后,就可以画出能够自动结构化的表格,在画表的界面上可以查看结果数据集的结构,随时确认是否合理。
与程序员画的填报表不同,业务人员不需要理解表格扩展和公式变迁这些比较抽象的规则,继续延用Excel的习惯画出静态的表格就可以了。一般情况下,设计程序会自动从表格中抽取出需要结构化的字段名,有特殊要求也可以自行设置属性。

采集上来的已经是结构化数据,我们假定业务人员不会在数据库中创建数据表,这些数据可以用集算器脚本保存到文件中。基于集算器的计算能力,借助超维报表的呈现控件,我们还提供了针对这些文件的分组汇总、切片过滤等类OLAP式的统计分析界面。这样,只要事先开发好应用程序,当有新的填报需求时,画表、下发、收集、分析等所有环节都不必再依赖于技术人员,业务部门可以完全自主地完成所有动作。

不过,如果希望把数据写入数据库,这还是需要技术人员协助在数据库中创建数据表并写入数据,对于已经结构化过的数据,这个工作很容易了。如果业务部门有人员对关系数据库的机制有足够理解,也可以自己做。