在数据的实际使用过程中,我们经常需要进行多维度的组合汇总,做一个数据立方体。常见的方法是在各个维度下进行group by,建立多个任务,这样不利于代码查看和维护,同时也浪费开发时间。
数据立方体与OLAP:http://webdataanalysis.net/web-data-warehouse/data-cube-and-olap/
有一张数据表的结构如下
字段名 | 字段类型 | 注释 |
---|---|---|
fserver_ip | string | 服务器ip |
finterface | string | 服务接口名 |
ferror_code | string | 错误码 |
fcall_num | int | 接口调用次数 |
fcall_time | int | 接口调用耗时 |
现在有一系列的查询需求
- 查看服务器ip、服务接口名、指定错误码下的接口调用次数、接口调用总耗时;
- 查看服务器ip下所有的接口调用次数、接口调用总耗时;
- 查看错误码下所有的接口调用次数、接口调用总耗时;
- 查看服务器ip、错误码下所有的接口调用次数、接口调用总耗时;
……
上面各种查询需求,都是针对不同维度进行组合统计的,用最笨的办法,可能需要写一堆group by xxx,但通过建立数据立方体,就可以在对某个(些)维度进行汇总统计时进行上卷等操作。
建立数据立方体之前,先要明确需要处理哪些维度、指标。下面是一个数据立方体
|
维度 | 指标 | |||
---|---|---|---|---|---|
排列组合 | fserver_ip | finterface | ferror_code | sum(fcall_num) | sum(fcall_time) |
不组合(明细) | |
|
|
|
|
维度1组合 | ✔️ | |
|
||
维度2组合 | |
✔️ | |
|
|
维度3组合 | |
|
✔️ | |
|
维度12组合 | ✔️ | ✔️ | |
|
|
维度13组合 | ✔️ | |
✔️ | |
|
维度23组合 | |
✔️ | ✔️ | |
|
维度123组合 | ✔️ | ✔️ | ✔️ | |
|
实际上对于每个维度而言,是“组合”与“不组合”两种可能,因此 M 个维度对应的组合数是 2M (或从排列组合角度,维度的组合数 = C0M+C1M+C2M+...+CMM=2M )
本文将使用hive streaming + python构建数据立方体
Hive Tranform方式的官方文档:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Transform
如上图,hive streaming方式实际上是将hive sql查询的记录,逐条传递给jar/python等进行处理。构建数据立方体的过程,是需要对每条明细数据处理 2M 次来生成各种维度组合,处理过程如下
cube.py
def binary_reflect_gray_code(n):
"""
生成n位的二进制反格雷码, 即n个元素的全组合
:param n:
:return:
"""
if n == 1:
return ["0", "1"]
L1 = binary_reflect_gray_code(n-1)
L2 = copy.deepcopy(L1)
L2.reverse()
L1 = ["0" + l for l in L1]
L2 = ["1" + l for l in L2]
L = L1 + L2
return L
if __name__ == "__main__":
not_group_set = []
if len(sys.argv) < 4:
print 'Error! Not Enough Params.'
print 'Usage: python cube.py <> '
sys.exit(1)
elif len(sys.argv) > 4:
_, field_cnt, dimension_cnt, split_sign = sys.argv[0:4]
not_group_set = sys.argv[4:]
elif len(sys.argv) == 4:
_, field_cnt, dimension_cnt, split_sign = sys.argv[0:4]
combination_list = binary_reflect_gray_code(dimension_cnt)
for line in sys.stdin:
kv = line.replace('\n', '').split('\t')
for combination in combination_list:
if combination not in not_group_set:
for item in range(int(field_cnt)):
if item < int(dimension_cnt):
if combination[item] == '0':
print '%s\t' % (kv[item]),
else:
print '%s\t' % (split_sign),
else:
print '%s\t' % (kv[item]),
print
参数 | 是否必传 | 样例 | 注释 |
---|---|---|---|
参数1 | √ | 5 | 指的是传入TRANSFORM的列数 |
参数2 | √ | 3 | 指的是前几列是维度,故而需要把维度放在前面 |
参数3 | √ | all | 指的是汇总列替代的值,比如all |
参数4-n | 选填 | 00 01 10 | 指的是不需要维度汇总的组合,支持多个组合。比如不需要第一列汇总,则写10和11(两位数字表示维度数为2,其中第一位1表示按第一个维度汇总,比如指定了10,则表示过滤按第一列汇总的数据) |
1) 新建文件cube.sql
,添加如下HQL脚本
add file cube.py;
select
fserver_ip,
finterface,
ferr_code,
sum(fcall_num) as fcall_num,
sum(fcall_time) as fcall_time
from (
from dp_monitor.t_monitor_acc
select TRANSFORM(fserver_ip, finterface, ferr_code, fcall_num, fcall_time)
using 'python cube.py 5 3 all' as
fserver_ip, finterface, ferr_code, fcall_num, fcall_time where fdate = '2017-10-16'
) tmp
group by fserver_ip, finterface, ferr_code
order by fserver_ip, finterface, ferr_code;
2) 使用hive命令行执行HQL脚本
hive -f cube.sql
本地导出数据进行测试:
hive -e "select fserver_ip, finterface, ferr_code, fcall_num, fcall_time from dp_monitor.t_monitor_acc where fdate = '2017-10-16' limit 100" > a.txt
cat a.txt | python cube.py 5 3 all
假设:
时间复杂度为: