作者:teachzhang 腾讯PCG工程师
|导语 大数据多维分析是业务中非常常见的分析场景,目前也有许多落地方案,但是在遇到上百亿数据、维度个数不限、秒级返回结果这样的场景时,实现的时候还是遇到了一些挑战。本文介绍了一种参考kylin的预聚合模式实现的存储方案,支持对上百亿数据以及数百个维度的多维分析,并且能在秒级返回查询结果。该方案可以运用于多维指标拆解分析,异动归因分析业务场景。希望给其他有类似分析场景的同学提供一种参考方案,对本内容感兴趣的同学,欢迎一起交流学习。
1. 背景
周报场景:微视的分析师每周一需要对上周的业务情况进行分析,整理成周报向领导进行汇报。以前的流程是在灯塔上通过执行大量复杂的sql查询需要的数据,然后下载数据,整理成许多多维分析树。查询的数据以周为单位,需要查询4周的数据,数据量达几百亿,因此每次查询都需要很长的时间(小时级),并且经常碰到查询超时的情况。当分析模型变动时,又得需要改动sql重新查询。总结就是,查询流程长,人工成本非常大,整个团队的分析师周一一大半时间都fork在周报整理上。
即席分析:当业务指标大幅变动时,需要快速找出变动原因,例如当DAU下跌时,分析师需要从多个维度钻取,找出具体的下跌原因。
除此之外,希望在微信小程序查看多维分析报表,从数据中发掘出更多的价值。
因此急需建设一个多维分析平台,解决目前的业务痛点。
2. 产品介绍
最终我们开发了一个归因分析平台,产品演示如下:
特殊说明:1.展示数据经过脱敏处理;2.为了演示直接使用了已运行好的报表。
功能特色:
- 零SQL实现数据多维分析,直接选择指标和维度,快速构建多维分析报表;
- 速度快,对于每天4000W数据量的用户报表,回溯30天只需5~10分钟;对于每天上亿的消费数据报表,回溯30天需要30~60分钟;
- 对于例行的报表,秒级返回结果;
- 精确设置每个节点数据波动范围,当时数据波动超过阈值,在报表上进行告警提示;
- 可以将分析报表以图片和excel的方式导出;
- 支持在小程序上查询报表,随时随地看数;
- 快速添加维度和指标,维度和指标支持逻辑计算,可以快速配置添加新指标和维度,对于大宽表存在的指标,无需回溯历史数据。
归因分析:
以上是分析师创建的归因报表(注:数据为假数据),用于分析拉活对内软下各渠道的影响。
3. 技术难点
要实现这样一个多维分析系统,有以下难点需要解决:
- 数据量大:每天的数据量数10亿,一次查询28天数据,数据量达几百亿;
- 时间范围不确定:查询的时间范围不确定,可能是一周的数据,也可能是任意选择的某几天数据。
- 维度多:目前常用的维度约50个,以后还会继续增加,上不封顶;
- 秒级查询:因为需要在小程序上支持多维分析,因此需要在1秒内返回分析结果;
- 动态增减维度和指标:随着业务的发展,会不断的添加维度和指标,并且需要支持自定义计算逻辑。
4. 实现思路
4.1 分析树拆解
首先我们来看一下一棵多维分析树是如果查询数据的。上面是一棵多维分析树,m1代表指标,例如DAU、总时长等,D1代表维度,例如城市、首启方式等。对于这棵树,转化成sql去查询数据,一次sql无法实现,只能多次查询才能取得结果,分为以下组合查询:
(D0)
(D1), (D2) (D1, D3), (D1, D4) (D1, D4, D5), (D1, D4, D6)
例如(D1, D3)转化成sql如下
select D1, D3, m1 from table group by D1, D3;
D0代表对全部聚合查询。对于一颗具有n个维度,m个指标的分析树,查询sql的次数为m*(n 1)次。
4.2 存储引擎调研
接下来需要选择一个合适的大数据分析引擎,目前常用的有ES、Spark、Kylin、Clickhouse、灯塔、Hermes,我们分别对这些引擎进行了调研,进行了对比:
- Spark:spark查询时间较长,不采用。
- 灯塔:灯塔提供对外接口,可以执行sql语句,但是经过测试,对一个几千万数据量的表,进行单维度、单指标聚合查询需要15s左右,无法满足要求。
- Kylin:Kylin采用预聚合的方式,提前将所有聚合维度和指标计算好,因此能实现亚秒级响应,查询时间上满足要求,但是Kylin对cube的维度有限制,正常情况在10个维度左右,本次的需求需要至少40个维度,以后还会继续增加。虽然Kylin提供了剪枝功能,但我们的场景中,分析师会选择任意的多个维度进行组合,所以无法利用剪枝进行优化,因此Kylin也无法采用。
- ElasticSearch:ES对于千万级数据的聚合查询可以很好的支持,一旦遇到较为复杂的多维度组合查询并且聚合的数据量比较大(如数十亿),就可能会产生大量的分组,对 ES 的性能压力很大,查询时间很长。并且本次需求中有许多指标需要进行distinct计算,然而ES不支持精确去重,并且也不好支持复杂的计算指标,因此不采用ES。
- ClickHouse:ClickHouse是一款PB级的OLAP数据库,采用列式存储,适合大数据多维分析,但是目前资源不够。申请资源至少需要1个月时间,且需要保存明细数据,需要大量的存储和计算成本。
- Hermes:Hermes是公司内部自研的一个实时秒级分析平台,采用类似ES的索引机制,支持sql查询,查询性能也不错(一次查询秒级),但是也无法支持上百亿数据的查询。
4.3 预聚合
通过对现有分析引擎的调研,发现它们都无法满足我们的业务场景。从查询时间要求出发,只有Kylin这种预聚合的引擎才能满足,但Kylin维度爆炸问题无法解决。以40个维度,最多5层维度组合为例,组合方式有C(40,5)=658008种,在上亿数据量的情况下Kylin无法聚合出结果。
通过对需求进一步分析,发现虽然维度非常多,但是分析师在使用中根本不会用到这么多维度组合,对于一个包含10个维度的报表,用到的维度组合为10,并且业务的分析逻辑基本上有迹可循,最终的维度组合最多只有几千。但是Kylin不支持指定维度组合的方式构建cube。因此我们决定参考Kylin的原理自己实现预聚合。
最终实现方案:将报表拆解为多个维度组合的cube,按天预聚合去查询每个cube的数据,将结果缓存起来。用户查询报表时,直接从缓存中取数。当新数据产出后,只需要增量缓存最新一天数据即可。
底层存储的选择上,只有Hermes和clickhouse比较适合,但是由于clickhouse现在资源紧张,并且业务方希望尽快上线,因此最终选择了Hermes作为查询存储引擎。
5. 实现方案
5.1 整体架构
整体架构分为四层
汇总层:根据前端传入的查询时间,按照本期、同比周期、环比周期、基期分别去聚合层查询分析树,然后将4颗树的数据汇总到一颗树上,计算出指标的同比、环比、同比等指标。
聚合层:解析多维分析树,拆分为多个cube,按照cube到聚合层查询数据,根据聚合指标的计算逻辑计算出指标绝对值,构造出一颗多维分析树。
预聚合层:维护所有分析树中用到的cube集合,自动调度cube任务,解析成sql语句,从Hermes查询聚合数据,最终缓存到ES。
源数据层:采用Hermes存储底层明细数据,利用Hermes的sql能力查询聚合数据;采用ES存储聚合数据,最终报表的查询直接从ES中查询缓存数据,做到秒级响应。
接下来重点介绍预聚合层的实现原理。
5.2 预聚合层
5.2.1 kylin的存储结构
因为本架构是参考Kylin实现的,所以首先介绍一下Kylin的存储结构。
Apach Kylin是一个开源分布式数仓,提供超大规模的多维分析能力,这里就不详细介绍了,网上有很多文章,这里重点介绍Kylin Cube的实现方式。Cube也就是多维立方体,提前将所有维度的可能的组合提前聚合分析好,以Cube为单位存储起来。
这里以查询DAU场景为例,假如有城市(city)、渠道(channel)、消费层次(consume_level)三个维度,那么所有维度的组合有2^3=8种。
一维度(1D)的组合有:(city)、(channel)、(consume_level)
二维度(2D)的组合有:(city, channel)、(city, consume_level)、(channel, consume_level)
三维度(3D)的组合有:(city, channel, consume_level)
另外还有(0D),总共8种。
假设有以下数据:
城市 | 消费层次 | 渠道 | qimei |
---|---|---|---|
北京 | 低 | c1 | 10 |
北京 | 低 | c2 | 20 |
北京 | 中 | c2 | 30 |
北京 | 中 | c4 | 40 |
上海 | 高 | c1 | 50 |
上海 | 高 | c3 | 60 |
现在计算DAU=count(distinct qimei),按照上面的方式计算所以维度组合
为了压缩存储,需要对维度和维度值进行编码,生成维度字典。
维度编码:
维度值编码:
最终对所以cube编码如下:
其中code和itemKey中的0代表全部。
5.2.2 预聚合层架构
首先给出cube和cube任务的定义。维度编码 指标代表一个cube,例如(101, m1);cube再加上日期代表一个cube任务,例如(20210101, 101, m1)。
目前预聚合层会缓存30天的数据,一个分析报表创建好之后,需要缓存近30天的数据,当所有数据都缓存好之后,该报表才可用。第二天新的数据产出后,预聚合层自动缓存最新的数据,然后报表的时间范围就更新到最新。即用户创建好报表后,以后无需操作,就能查询最近一个月的数据。
创建一个报表的流程如下:
- 用户在界面上拖拽维度和指标构建多维分析树,创建好之后保存报表,点击“运行”按钮,此时报表状态为“运行中”;
- 将分析树拆分成多个cube,插入cube集合,如果cube已经存在则无需再次插入。
- 预聚合层定时调度,检测cube集合中的每个cube,是否最近一个月都有数据,如果没有,则创建相应日期的cube任务。
- 任务执行器执行cube任务,解析成sql,去Hermes查询聚合数据,然后对结果进行编码,最后将结果存储到ES当中。
- 当一个月数据缓存完成后,更新报表状态为“运行成功”,此时报表就可以查询数据了。
5.3 其他功能
缓存数据多版本:对于同一cube的数据,可以存在多个版本。这是因为当一个报表的数据缓存好之后,如果此时某几天数据出问题了需要重跑,那么需要将所有数据重跑好了之后,再一次全部切换,否则会让用户看到中间态的数据。
动态添加指标和维度:可以根据现有的维度和指标配置出新的维度指标,无需重跑源表数据。
支持留存维度和指标:支持不同留存时间的维度和指标,比如次留、3日留存、7日留存等等。
6.未来优化
1.优化底层存储。目前支持的数据分析时间范围为一个月,未来可能会增加到3个月或者半年,相应的缓存时间就会变长,Hermes的存储成本也会变大,未来准备支持多个数据底层数据源,比如灯塔、presto、clickhouse等,加速缓存。
2.添加各种归因算法。未来准备在归因分析树上应用一些归因算法,比如JS散度等,智能的找出指标的影响因素。
3.添加报表全局过滤功能。在报表上添加过滤条件,限定整棵树的分析范围。
4.支持维度间的层级关系。比如支持城市、区域维度之间的层级关系。
近期热文
【Node开发】分布式调用限频限流的开发设计
解决单点故障 - 有状态服务的高可用
如何输出有价值的商业解决方案?
让我知道你在看