报表性能是总也避不开的话题,报表作为 OLAP(在线联机分析)中的主要应用场景,无论从涉及数据的宽度(表数量),还是数据的广度(查询范围)都可能非常巨大;而且在报表中还经常伴随非常复杂的数据处理逻辑,这些都会影响报表的运行速度。而服务器环境、数据库环境、JDBC 效率、网络环境、客户端环境这些也都都跟报表性能密切相关。
报表性能可能跟很多因素有关,非常复杂。这里我们试着从报表运行的各个阶段来分析报表性能问题产生的主要原因及其应对方法。未尽之处,欢迎讨论。
我们知道报表运行主要分报表解析、数据准备、数据传输、报表计算和报表生成 5 个阶段。除了报表解析是引擎加载解析模板,还未开始运算外,其他 4 个阶段(示意图中黄色的部分)均可能引起性能问题。
我们在分析报表性能问题时一定要先定位是哪个阶段引起的,抓主要矛盾。定位的方法也很简单,就是分析报表运行日志,很多报表工具都会输出各个阶段的运算时间,看看哪个阶段耗时最长,就是问题发生的主要阶段了。
1. 数据准备
报表数据准备是指从数据源中读取数据并将数据组织成报表可用的结果集的过程。在报表中往往是以数据集的形式存在,可以通过 SQL、存储过程或 JAVA 实现。
数据准备并不是简单地将数据源的原始数据取出就结束了,而是会伴随一些计算过程,有些还很复杂,可以想想一下平时我们开发报表时编写的 SQL 绝大多数情况下都不是简单的 select * from tbl。如果这个 SQL 比较复杂(由于大多数情况都是使用 SQL 准备数据,所以这里我们仅以 SQL 为例,其他数据源可以参考 SQL 数据集的解决办法)运行较慢,就会导致报表变慢。
这个阶段在数据库中运行,本质上跑的快慢都跟报表无关,但好的报表工具是可以干预优化这个过程的(后面再说)。
数据准备阶段慢,可以先试着优化 SQL,但 SQL 优化往往复杂度较高,所以也经常采用预计算的方式进行性能优化(这里不讨论硬件升级、数据库扩容等物理优化手段)。
(1) 优化 SQL
我们知道复杂 SQL(关联多、嵌套多)是比较难优化的。数据库的透明化机制让写 SQL 时不用关心底层的执行顺序,由数据库优化器自动执行,这样可以简化编写 SQL 的难度,但过于透明的机制让我们很难干预 SQL 的执行路径,也就很难优化 SQL 了。
(2) 预计算
SQL 无法优化或优化效果不理想时,通过预计算可以提升报表数据准备效率。将报表需要的结果集事先加工出来存储到中间表中,报表查询时直接读取加工好的结果集,这样就可以节省大量计算时间,从而提升报表性能。预计算本质是用空间换时间的手段。
不过,预计算的缺点也非常明显,你不妨先思考一下有哪些问题?
主要的问题有两个:时间问题和空间问题。
时间问题
预计算需要“事先”加工,其本质是一个 ETL 过程,这样就会引起两个时间问题,数据的实时性和预计算的时间成本。
报表基于预计算的结果查询只能查询预计算时点以前的数据,无法查询预计算到当前时间的数据,这样就导致了数据时效性差,不适合数据实时性要求高的业务场景(比如交易系统)。
预计算往往会放到业务空闲的阶段进行,比如前一天夜里到第二天上班前,通常还要预留一些时间容错(跑失败了要重新跑),这样可能留给预计算的时间也就 5、6 个小时,这个时长基本是固定不变的,而数据规模、业务复杂度、报表数量都会不断增长,未来极有可能会引发预计算跑不完而影响业务使用的问题。
空间问题
预计算的结果是要落地的,往往会存储在数据库物理表中,这样的表多了会占用大量数据库空间,引起数据库容量问题;另外这样的表多了还会带来管理上的问题。
预计算的应用范围很窄,而 SQL 又比较难优化,还有什么其他优化手段吗?
(3) 借助其他高性能计算引擎
在数据库内搞不定报表数据准备阶段优化时可以引入其他高性能计算引擎,这在业内并不少见,但几乎所有技术都是采用独立存储 + 分布式架构来输出高性能的,这对报表应用架构(主要是数据库)的改变就有些大了,难度很大。
比较简单直接的方式是在报表内部就能提供改变 SQL 执行路径的手段,并且可以改善那些显著低效的 SQL 算法(比如 topN),这样就可以在不改变应用架构的情况下实现数据准备阶段的优化。
这就要求报表工具提供数据准备阶段的计算能力,既可以分步执行 SQL(指定执行顺序),还可以改善算法效率。其表现形式可以是一种内置在报表工具中的计算脚本,脚本具备分步计算和强计算能力。
报表工具计算模块提供可干预 SQL 执行路径能力和高效的强计算能力
2. 数据传输
报表通过 JDBC 接口访问数据库读取所需数据时,如果数据量比较大或者数据库 JDBC 性能较差(要知道各种数据库的 JDBC 效率是不同的)会导致数据传输时间过长,导致报表变慢。
由于我们没法改变数据库的驱动,我们只能在报表层面想办法。一个可行的办法是通过并行取数来提速。
(1) 并行取数
通过建立多个数据库连接(这时要求数据库相对空闲),采用多线程的方式同时读取报表所需数据,可能是同一个表,也可能是多个表关联计算后的结果,这样数据传输的时间理论上就会缩短到原来的 1/n(n 是线程数),从而提升报表性能。
那么这种并行取数实现起来难度如何呢?
因为目前大部分报表工具不支持并行取数,要想通过并行来加速数据传输只能自己使用 JAVA 硬编码来做。我们曾经讨论过在报表中使用 JAVA 硬编码准备数据的弊端,而编写并行程序难度又提升了一级,要知道,并行以后还要归并(merge)各个线程的计算结果,merge 不是简单地纵向拼接就完了,有时还涉及分组和顺序。
使用支持并行取数的报表工具
比较简单有效的方式是使用支持并行取数的报表工具,报表开发人员不需要考虑数据资源冲突、结果归并等复杂问题就可以简单实现。
(2) 异步传输
当数据量较大时,可以通过异步机制将数据分批返回给报表,报表接收部分数据后就立刻呈现,后台同时进行不间断的数据传输,这样就可以提升报表的呈现速度。
异步传输需要考虑很多方面,不建议自己硬编码实现,最好使用支持异步传输功能的报表工具,简单快捷。
3. 报表计算
数据传输完成后,报表引擎会根据已准备数据和报表表达式运算报表,这个阶段也是非常容易出现性能问题的。
性能问题常见于多数据集报表。
现在很多报表工具都提供了多数据集能力,允许开发者在一个报表中建立多个数据集,这样可以分别组织数据,尤其当数据来自不同数据库时,多数据集尤其方便。但这种方式为报表开发带来便利的同时却会带来很严重的性能问题。
我们知道,多数据集的关联是在报表单元格的表达式中完成的,类似这样 ds2.select(ID==ds1.ID),报表引擎在解析这个表达式时会按照顺序遍历的方式完成关联,即从 ds2 中拿出一条记录,到 ds1 中遍历,查找 ID 相同的记录;然后再拿第二条再去遍历查找;…
这个运算复杂度是平方级的,数据量小的时候没什么影响,数据量稍大时性能就会急剧下降。
另外,报表单元格还带有大量呈现属性(颜色、字体、边框等),带着这些属性运算也会拖慢报表的运行速度。所以,对于数据量较大的多个数据集,最好在数据准备阶段使用更高效的关联算法完成关联。
在数据准备阶段完成关联
将多数据集关联转移到数据准备阶段完成,通过 SQL 就可以实施更加高效的关联,只是 SQL 只能基于单一数据库操作,如果数据来自多个数据源时就不灵了。这时,最好报表工具在数据准备阶段还提供诸如 HASH JOIN 等高性能关联算法,以便满足异构数据来源时的关联运算。HASH JOIN 算法可以整体地看待几个数据集,效率比报表工具采用的过滤式关联要高得多,几千行规模时几乎是零等待。
4. 报表生成
报表计算完成后会生成引擎会将计算结果以 HTML 的形式输出,生成最终要呈现报表。这个阶段的性能问题主要有两方面。
(1) 页面渲染慢
当报表中添加了大量的呈现效果(隔行异色、背景图、警戒色等)时,页面渲染的速度就会变慢。减少过多的报表效果使用可以提升渲染速度;如果这些效果是必须的,那就要比拼使用的报表工具的能力了。
另外,如果报表单页的内容过大也会影响页面呈现的速度。对于比较高或者比较宽的报表呈现时建议采用分页方式输出,从而提升页面呈现效率。
(2)客户端环境差
总体上是跟用户端环境有关系,比如网络问题、设备问题等,遇到时需要具体分析。
通过了解报表运行各阶段可能出现的性能问题,以及对应解决办法,有些我们可以硬编码或借助其他技术来解决,有些还需要报表自身来提升。最直接的方式是使用能够提供这些能力的报表工具,这个工具大概要具备这些特性才能帮助我们有效解决报表性能问题。
1. 支持分步
这样可以有效优化 SQL 执行路径,提升数据准备效率;
2. 内置强计算能力和高效算法
能够改善原有低效的算法,提高数据计算效率;
支持 HASH JOIN 等高性能关联算法,以减少在呈现模板中进行多数据集关联;
3. 支持并行取数
能够通过多线程并行方式提升数据传输速度效率;
4. 支持异步读取
提供异步取数异步呈现机制,加速大数据量报表的呈现效率;
5. 支持多数据源
提供多样性数据源接口,并能够进行多数据源关联运算(同 2)。
参考资料:
网友评论