报表数据源的热切换技术

报表业务的稳定性差,报表数据来源(数据准备)经常需要修改,修改后的报表能否做到不停机热切换(HotSwap)对报表应用来说十分重要。本文将探讨几种报表数据源的热切换技术并加以对比。

SQL

基于数据库使用 SQL 实现报表数据准备比较容易实现热切换。大部分报表工具呈现模板都是解释执行的,开发人员修改数据准备 SQL(语句通常内嵌在报表模板中)后更新模板可以实时生效,从而实现热切换。存储过程相对复杂一些,修改报表中的存储过程大部分情况下数据库会自动编译(初次访问时),但遇到一些特殊情况时则需要手动编译,这时就达不到热切换的效果了。

不过,由于大部分数据库只能针对库内数据运算(有些数据库支持外部表,但限制较多,如不支持索引、只读、性能不高等),无法应付多样性数据源混合计算的报表场景。SQL 面向复杂计算是编写难度高,而各个数据库对存储过程支持的程度不同(有的根本不支持存储过程)这些都限制了 SQL 的使用场景。

此外,有时为了应用获得良好的扩展性和移植性,不会过于依赖数据库的计算能力,不再使用 SQL 尤其是存储过程完成数据计算,而将数据处理逻辑上移到应用层完成,报表应用也不例外。因此,SQL 可以作为特定场景下实现报表数据源热切换的技术,但并不十分通用。

报表工具

有些报表工具提供了一定的数据准备能力,诸如脚本数据源(Script Data Source)的功能,开发人员可以编写脚本完成报表数据准备。脚本一般使用 JavaScript(JS)语法,脚本实时修改实时生效,这样就达到了热切换的效果。不过由于 JS 缺乏专门面向结构化数据的计算库,实现复杂报表数据准备会非常吃力,这时还需要借助 Java 的能力。

以 BIRT 为例,BIRT 提供了一种脚本数据源类型,建立脚本数据源后在 open 脚本中完成引入类库、定义变量等操作,然后在 fetch 脚本中实施数据准备,自定义脚本可以处理不同数据来源的数据,相对 SQL 更开放一些。
birt1png

数据源准备好后创建数据集,此时需要手动维护一个与数据源相同结构的数据集,包括列名、数据类型都要一致才可以。很显然,如果报表涉及动态数据结构就很不方便了。

imagepng
数据集的结构要与脚本数据源一致

在脚本数据源中还可以引入 Java 类借助 Java 的计算能力从而实现更复杂的数据处理过程,但 JAVA 作为编译型语言又很难做到热切换了(关于 JAVA 热切换我们会在下文讨论)。

从上面的过程可以看到,相对 SQL 方式脚本数据源虽然可以对接多样性数据但数据处理能力又有所欠缺,同样只能针对特定场景(简单计算)。即使如此,也并不是所有报表工具都提供脚本数据源这样的数据准备能力,要实现数据源热切换还要借助其他方式。

Java

大部分报表工具都会提供数据源扩展接口以方便用户自定义数据处理逻辑,这里以使用最多的 Java 接口探讨报表数据源的热切换技术。

Java 作为编译型语言必须编译后才能运行,关于 Java 热切换(HotSwap)的话题从 JDK1.4 开始就反反复复讨论过很多次,一直没有很好的解决办法。下面讨论三种目前比较常见的 Java 热部署技术。

Java 默认不支持热切换, JavaClass 通过 ClassLoader 加载后执行,Class 在 ClassLoader 的 namespace 下有唯一标识,单个 ClassLoader 无法多次加载相同标识的 class,重启就是为了 ClassLoader 重新加载新的 class。这时我们就可以定义不同的 ClassLoader,监听文件变化后,通过新的 ClassLoader 加载新文件,然后做好相应的状态恢复,对旧 ClassLoader 进行卸载等动作。Tomcat 的动态部署其实就是监听 war 变化,然后调用 StandardContext.reload(),用新的 WebContextClassLoader 实例来加载 war,然后初始化 servlet 来实现。类似的实现还有 OSGi 等。

还可以使用诸如 JRebel 或 spring-loaded 实现 Java 热切换,这类技术是利用 ClassLoader 加载 Class,然后利用字节码 instrument 技术实时修改字节码,相当于被加载的 Class 被框架的 Runtime 代理了,通过其 Runtime 就可以实现相应的热切换。但是 JRebel 的配置和使用并非十分简单,具体可以参考:https://www.jrebel.com/

还有一种方式,从 VM 层面修改 VM 彻底支持 Class 的动态性,这就要提到 dcevm 了。Dcevm (DynamicCode Evolution Virtual Machine) 是 java hostspot 的补丁,允许在运行时无限制地重新定义加载的类从而实现热切换。具体 dcevm 的使用可以参考:https://github.com/dcevm/dcevm。不过,应用门槛仍然很高。

整体来看要实现 Java 热切换无论自己编码实现热编译技术还是借助其他工具实现都非常繁琐,应用到报表的数据源准备中就太重了,也不适合报表开发人员使用。那还有什么技术吗?

集算器 SPL

集算器是一款轻量级开源计算引擎,擅长结构化数据计算,计算类库丰富可以满足复杂报表数据准备工作。集算器的作用以及在报表应用中的位置与脚本数据源和 Java 自定义数据源相当,介于数据源和报表呈现之间承担数据准备工作。同时,集算器支持多种数据源(RDB、NoSQL、Json、CSV、Webservice 等),可以实现跨数据源的混合计算。

集算器提供了独立的计算语法 SPL 可以分多步完成报表数据准备工作,SPL解释执行可以很好实现报表数据源热切换。

SPL 的语法很简洁,比如下面的例子:

根据股票记录表查询股价连续上涨超过 5 天的股票及上涨天数(股价相等记为上涨)并以报表呈现。

SPL 实现如下(stock.dfx):


A


1

=connect@l("orcl").query@x("select *   from stock_record order by ddate")


2

=A1.group(code)


3

=A2.new(code,~.group@i(price<price[-1]).max(~.len())-1:maxrisedays)

计算每只股票的连续上涨天数

4

=A3.select(maxrisedays>=5)

选出符合条件的记录

报表工具集成集算器 SPL 也很简单,只需要引入以下 3 个 jar 包

esproc-bin-xxxx.jar //集算器计算引擎及JDBC驱动包

icu4j-60.3.jar //处理国际化

jdom-1.1.3.jar //解析配置文件

建立集算器 JDBC 数据源连接(信息如下):

JDBC Driver:com.esproc.jdbc.InternalDriver

JDBC URL:jdbc:esproc:local://

最后在报表工具数据集中调用 SPL 脚本(类似调用存储过程的方式,支持传参)即可:

 {call stock()}

 

综合来看,实现报表数据源热切换的技术中,数据库随应用结构的发展变化越来越多地承担数据存储而非计算工作,SQL(包括存储过程)可以用但适用场景不够宽泛;报表工具自带的脚本数据源功能并非所有工具都支持且计算能力有限,与 SQL 一样虽然通用但适用场景有限。Java 的普适性最强可以应对所有数据计算场景,但热部署实施难度过高,而且 Java 本身缺乏集合运算类库编码难度也不低。集算器 SPL 解释执行天然支持热切换,SPL 语法足够简洁计算能力与 Java 相当(报表数据源方面)但应用难度却很低,可以将其作为报表数据源热切换的通用解决方案使用。