现在要搞一些数据分析(OLAP)相关的数据,恰好mondrian提供了一个用于测试的footmart数据集,这个数据库主要记录了一些关于销售数据的事实表和维度表,内容很丰富,并且提供了foorMart.xml文件,这个文件定义了所有需要使用的cube的定义集合,但是我们现在用自己开发的系统定义cube,所以这个配置文件应该就用不上了,主要需要生成这个数据库的数据,首先将它放到mysql上吧,然后再将它通过sqoop导入到hive中,用我们的OLAP报表系统定义cube和报表执行查询,这一套流程走下来基本上能把OLAP查询的基本流程走通了,接下来的主要工作还是在如何优化多维查询的性能。
首先这个数据库数据的生成是通过程序执行得到的,首先我们需要下载mondrian的某个版本(根据官方文档),下载地址在http://sourceforge.net/projects/mondrian/,但是有个问题就是在3.8.0版本之后只能找到jar包了,不能够找到完整的那个zip包,所以我就舍弃了最新的版本,下载一个比较新的3.7.0版本的zip包(http://sourceforge.net/projects/mondrian/files/mondrian/mondrian-3.7.0/mondrian-3.7.0.0-752.zip,大小为119M),解压之后在testsrc/main/mondrian/test/loader目录下有一个MondrianFoodMartLoader.java文件,这个java程序就是为了生成测试数据的,当然测试数据都是存放在一个.sql文件里面,这个文件在demo目录下,文件名是FoodMartCreateData.zip,将它解压过之后,编译MondrianFoodMartLoader程序,然后运行如下命令:
-verbose -tables -data -indexes -jdbcDrivers=com.mysql.jdbc.Driver -outputJdbcURL=jdbc:mysql://172.17.3.102:16666/foodmart -outputJdbcUser=root -outputJdbcPassword=root -outputJdbcSchema=foodmart -outputJdbcBatchSize=50 -inputFile=C:\Users\Administrator\Desktop\FoodMartCreateData.sql
根据参数名可以看出参数的含义分别是:
-verbose 详细输出日志信息
-tables 如果表不存在则创建表
-data 如果数据存在则删除全部数据在load,如果不选则不导入数据
-indexes 决定是否创建索引
-jdbcDrivers 数据库的driver
-outputJdbcURL 导出的数据库url
-outputJdbcUser、 -outputJdbcPassword 导出数据库的用户名和密码
-outputJdbcSchema 目标数据库名,如果不指定则使用默认的
-outputJdbcBatchSize 是否批量插入,默认是50条记录一批进行插入
-inputFile 导入的数据文件,就是之前在mondrian中提供的解压之后的.sql文件路径
创建完成之后有如下一些表:
mysql> show tables; +-------------------------------+ | Tables_in_foodmart | +-------------------------------+ | account | | agg_c_10_sales_fact_1997 | | agg_c_14_sales_fact_1997 | | agg_c_special_sales_fact_1997 | | agg_g_ms_pcat_sales_fact_1997 | | agg_l_03_sales_fact_1997 | | agg_l_04_sales_fact_1997 | | agg_l_05_sales_fact_1997 | | agg_lc_06_sales_fact_1997 | | agg_lc_100_sales_fact_1997 | | agg_ll_01_sales_fact_1997 | | agg_pl_01_sales_fact_1997 | | category | | currency | | customer | | days | | department | | employee | | employee_closure | | expense_fact | | inventory_fact_1997 | | inventory_fact_1998 | | position | | product | | product_class | | promotion | | region | | reserve_employee | | salary | | sales_fact_1997 | | sales_fact_1998 | | sales_fact_dec_1998 | | store | | store_ragged | | time_by_day | | warehouse | | warehouse_class | +-------------------------------+
可以看出这里面的store_sales、store_cost、unit_sales可以作为度量的列,然后将他们使用某些聚合方式计算就可以作为度量了。其余的例如product_id、time_id、customer_id、promotion_id、store_id是其它维度表的主键,通过这个id来关联维度表,例如与time_id关联的表是time_by_day,表结构如下:
| time_by_day | CREATE TABLE `time_by_day` ( `time_id` int(11) NOT NULL, `the_date` datetime DEFAULT NULL, `the_day` varchar(30) DEFAULT NULL, `the_month` varchar(30) DEFAULT NULL, `the_year` smallint(6) DEFAULT NULL, `day_of_month` smallint(6) DEFAULT NULL, `week_of_year` int(11) DEFAULT NULL, `month_of_year` smallint(6) DEFAULT NULL, `quarter` varchar(30) DEFAULT NULL, `fiscal_period` varchar(30) DEFAULT NULL ) ENGINE=NTSE DEFAULT CHARSET=utf8 |
这样我们就可以设定time维度,这个维度关联的维度表就是time_by_day ,可以选取该表中可以枚举的列作为维度的级别(例如the_month、the_day、the_year、day_of_month、week_of_year等),这些级别之间可以有层级的关系,某一个级别是另外一个级别的父级,在当前的项目中一个维度下只支持一个级别有一个父级和一个子级的关系。例如year这个级别对应的是‘the_year’这一列,它的子级可以是‘quarter’列作为级别,例如在当前的表中‘the_year’这一列只有两个取值:
mysql> select distinct the_year from time_by_day; +----------+ | the_year | +----------+ | 1998 | | 1997 | +----------+
而‘quarter’列有四个取值:
mysql> select distinct quarter from time_by_day; +---------+ | quarter | +---------+ | Q1 | | Q2 | | Q3 | | Q4 | +---------+
当设定这个级别之间的关系之后,可以在指定year这个级别之后对其执行下钻操作,这样就会计算year=1997下quarter为每一个取值时的度量值,因此,级别的设定主要是为了设定对于同一个维度不同粒度的聚合方式(按照年、按照月or按照季度)。当然有了这个层级的关系,为上卷和下钻也提供了操作的基础。
在cube的定义中主要的就是维度和度量的定义了,当然在表里面我们可以看到一些以agg开头的表,这些表的创建是为了mondrian执行的时候优化的,在mondrian提供的FoodMart.xml文件中可以看到文件的开头制定了一些聚合表,这些聚合表相当于对于一些度量按照不同维度执行的预计算,当执行查询的时候如果匹配可以从聚合表中查询而不用再进行动态的计算,当然这样的与计算对于数据更新还是有一定的代价的。
目前基于mondrian的OLAP查询引擎面临的最大问题就是性能的问题,因为大部分的mdx查询操作都是动态得翻译成sql,然后从元数据库中执行查询操作,这难免会造成性能低下,尤其是对于不面向响应时间的hive数据库,查询速度那是相当的慢,目前的优化方式大体上是执行预计算的方式,在创建cube的时候预先将整个cube或者可以支撑整个cube的所有度量值都计算出来,查询的时候从预计算的结果中查询或者再进行聚合,由于预计算的结果可以存放在内存或者性能更高的key-value数据库中,这种方式比直接从元数据库查找的性能会有一定的提升,但是当数据量比较大的时候预计算的结果可能会非常大,如果节省缓存的空间也是面临的一个大问题。