本文主要讲解ROLAP,以及ROLAP的实现Mondrian的使用和部署问题。目前网上关于Mondrian部署的帖子较少,连官网也是若干年前的版本,因此,开贴记录一下Mondrian在使用上的一些坑,关心Mondrian的使用请直接移步第二章。
1.OLAP简介
1.1什么是OLAP
OLAP系统,英文为OnLine Analytical Processing,中文为联机分析处理系统,它在数据分析和决策方面为用户或“知识工人”提供服务,以不同的格式组织和提供数据,满足不同用户的形形色色的需求。与之相对的为OLTP(Online Transaction Processing,联机事务处理)系统,该系统主要用于单位的大部分日常操作,如购物、库存、制造、银行、工资、注册、记账等。
OLAP和OLTP的主要区别如下:
- 用户:OLTP是面向顾客的,用于事办员、客户和信息技术专业人员的事务和查询处理。OLTP是面向市场的,用于知识工人(包括经理、主管和分析人员)的数据分析。
- 数据内容:OLTP系统管理日常的、琐碎的数据,而这种数据一般很难用于决策。OLAP系统管理大量的历史数据,提供汇总和聚集机制,并在不同的粒度层上存储和管理系统。这些特点使得数据更容易用于有根据的决策。
- 数据库设计:通常,OLTP系统采用实体-联系(ER)数据模型和面向应用的数据库设计。而OLAP系统通常采用星形或雪花模型和面向主题的数据库设计。
- 视图:OLTP系统主要关注一个企业或部门内部的当前数据,而不涉及历史数据或不同单位的数据。相比之下,由于单位的演变,OLAP系统尝尝跨越数据库模式的多个版本。OLAP系统还处理来自不同单位的信息,以及由多个数据库集成的信息。
- 访问模式:OLTP系统的访问主要由短的原子事务组成。这种系统需要并发控制和恢复机制。然而,对OLAP系统的访问大部分是只读操作,大多数是复杂的查询。
OLTP和OLAP的主要区别之一在于数据库的选择,由于OLTP系统是面向顾客的、需要并发控制和恢复机制,因此OLTP系统往往采用关系数据库,如MySQL、Oracle数据库等。而OLAP系统一般是只读的、大量历史数据的汇总和聚集,因此OLAP往往使用数据仓库。
宽泛地讲,数据仓库是一种数据库,它与单位的操作数据库分别维护。数据仓库系统允许将各种应用系统集成在一起,为统一的历史数据分析提供坚实的平台,对信息处理提供支持。面向主题的、集成的、时变的、非易失的是数据仓库的主要特征,也是将数据仓库与其他数据存储系统(如关系数据库系统、事务处理系统和文件系统)相区别。
- 面向主题的(subject-oriented):数据仓库围绕一些重要主题,如顾客、供应商、产品和销售组织,关注决策者的数据建模与分析,拍除对于决策无用的数据,提供特地主题的简明视图。
- 集成的(integrated):构造数据仓库通常是将多个异构数据源,如关系数据库、一般文件和联机事务处理记录集成在一起。
- 时变的(time-variant):数据存储从历史的角度提供信息。数据仓库中的关键结构都隐式或显示地包含时间元素。
- 非易失的(nonvolatile):数据仓库不需要事务处理、恢复和并发控制机制。通常,它只需要两种数据访问操作:数据的初始化装入和数据访问。
通常,数据仓库采用三层体系结构:
1.2 为什么需要使用分离的数据仓库
分离的主要原因是有助于提高两个系统的性能。操作数据库是为已知的任务和负载设计的。另一方面,数据仓库的查询通常是复杂的,涉及大量数据在汇总级的计算,可能需要特殊的基于多维视图的数据组织、存取方法和实现方法。在操作数据库上处理OLAP查询,可能会大大降低操作任务的性能。此外,操作数据库支持多事务的并发处理,需要并发控制和恢复机制,以确保一致性和事务的鲁棒性。通常,OLAP查询只需要对汇总和聚集数据记录进行只读访问。最后,数据仓库与操作数据库分离是由于这两种系统中数据的结构、内容和用法都不相同。
1.3 OLAP服务器的结构:ROLAP、MOLAP和HOLAP
用于OLAP处理的数据仓库服务器的实现包括了ROLAP(关系OLAP)、MOLAP(多维OLAP)和HOLAP(混合OLAP),ROLAP服务器是一种中间服务器,介于关系的后端服务器和客户前端工具之间,它们使用关系的或扩充关系的DBMS存储并管理数据仓库数据,而OLAP中间件支持其余部分,相应的实现有Mondrian等;MOLAP服务器通过基于数组的多维存储引擎,支持数据的多维视图,它们将多维视图直接映射到数据立方体数组结构。使用数据立方体的优点是能够对预计算的汇总数据快速索引,相应的实现有Cognos,SSAS,Kylin等;HOLAP服务器结合ROLAP和MOLAP技术,得益于ROLAP较大的可伸缩性和MOLAP的快速计算,比如微软的SQL Server2000。它们的区别的表格如下:
名称 | 描述 | 细节数据存储位置 | 聚合后的数据存储位置 | 代表 |
ROLAP | 基于关系数据库的OLAP实现 | 关系型数据库 | 关系型数据库 | Mondrian |
MOLAP | 基于多维数据组织的OLAP实现 | 数据立方体 | 数据立方体 | Cognos,SSAS,Kylin |
HOLAP | 基于混合数据组织的OLAP实现 | 关系数据库 | 数据立方体 | SQL Server |
个人认为上面的几种方式没有绝对的优劣之分,选择哪种结构还是和具体的应用有关。
2.Mondrian+JPivot
Mondrian是ROLAP的实现之一,它是搭配构建数据立方体的XML文件,根据访问的MDX语句动态查询数据库,在内存中存储数据立方体,并返回结果,因此,Mondrian对内存的要求较高。
官网地址:https://mondrian.pentaho.com/documentation/olap.php。通过查看官网,你会发现,Mondrian是提供了war包的,然而,官网有关于[安装]的内容应该滞后若干个版本,也就是说,单纯的Mondrian只是提供了jar包,如果想要使用线上展示结果的功能,需要搭配JPivot一起使用,JPivot提供war包,它需要Mondrian的jar包,然后配合jsp代码来实现网页的可视化。有关于Mondrian和JPivot的环境配置,请参考参考文章的第三个Mondrian + JPivot 环境配置。从https://pan.baidu.com/s/1zkSyZJkTGabEFwcfsktDCw 下载 mondrian server.rar , test-mondrian.rar。本小节主要讲解JPivot的粗略的运行方式。
test-mondrian.rar为控制台运行的方式,运行Main.java,输出如下:
Axis #0:
{}
Axis #1:
{[Measures].[Amount]}
Axis #2:
{[AccessTime].[AllTime], [WebsiteNum].[AllSite], [UserIP].[AllIP]}
Row #0: 278
Main.java中的MDX语句是查询所有访问记录的总次数,查询结果为278。但是控制台查询的弊端在于不够直观,比如MDX语句为:
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales], [Measures].[Profit]} ON COLUMNS,
{descendants([Time].[1997].[Q1])} ON ROWS
FROM [Sales] WHERE [Gender].[F]
旨在查询1997年第一季度的所有月份,女性的消费记录,输出如下:
Axis #0:
{[Gender].[All Genders].[F]}
Axis #1:
{[Measures].[Unit Sales]}
{[Measures].[Store Sales]}
{[Measures].[Profit]}
Axis #2:
{[Time].[1997].[Q1]}
{[Time].[1997].[Q1].[February]}
...
{[Time].[1997].[Q1].[March].[15].[Sunday]}
Row #0: 32,910
Row #0: 69,798.23
Row #0: $41,976.46
Row #1: 10,266
Row #1: 21,773.93
Row #1: $13,110.09
Row #2: 207
Row #2: 414.6
Row #2: $246.12
Row #3: 207
Row #3: 414.6
Row #3: $246.12
Row #4: 2,871
Row #4: 5,940.83
Row #4: $3,580.08
...
Row #109: 206
Row #109: 405.37
Row #109: $239.28
那么控制台方式的输出方式的缺点则更加凸显,接下来,则开始讲解JPivot。
参考文章3已经给出了一个简单的demo,该示例为上述输出的jsp版本。接下来主要分析,在jpivot.war的基础上,如何自行添加相关的jsp页面。这里假设tomcat已经处于运行之中,那么输入http://127.0.0.1:8080/jpivot/index.jsp,页面如下:
在上述页面中,AccessInfo为压缩包中的Sample Demo,FoodMart3为第二个MDX语句的输出结果,首先观察index.jsp,该文件在webapps\jpivot文件夹下,打开:
...
<ul>
<li><a href="testpage.jsp?query=mondrian">Slice and Dice with two hierarchies</a></li>
<li><a href="testpage.jsp?query=fourhier">...and with four hierachies</a></li>
<li><a href="test/param3.jsp?query=mondrian">Dynamic parameters with Mondrian</a></li>
<li><a href="testpage.jsp?query=arrows">Arrows in Cells</a></li>
<li><a href="testpage.jsp?query=colors">Colors in Cells</a></li>
<li><a href="testpage.jsp?query=testquery">Test data</a></li>
<li><a href="testpage.jsp?query=AccessInfo">AccessInfo</a></li>
<li><a href="testpage.jsp?query=FoodMart3">FoodMart3</a></li>
<li><a href="test/param1.jsp?query=testquery">Dynamic parameters with test data</a></li>
</ul>
...
通过对比网页发现,<li><a href="testpage.jsp?query=AccessInfo">AccessInfo</a></li>为入口URL,那么接着打开testpage.jsp,第44行:
<%-- include query and title, so this jsp may be used with different queries --%>
<wcf:include id="include01" httpParam="query" prefix="/WEB-INF/queries/" suffix=".jsp"/>
prefix为前缀,suffix为后缀,再结合传入的query,整合起来就是 /WEB-INF/queries/AccessInfo.jsp,接着打开该文件:
<%@ page import="mondrian.olap.*"%>
<%@ page session="true" contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<jp:mondrianQuery id="query01" jdbcDriver="com.mysql.jdbc.Driver" jdbcUrl="jdbc:mysql://localhost/jpivot" catalogUri="/WEB-INF/queries/AccessInfo.xml" jdbcUser="jpivot" jdbcPassword="jpivot" connectionPooling="false">
select NON EMPTY {[Measures].[Amount]} ON COLUMNS,
NON EMPTY{([AccessTime].[AllTime], [WebsiteNum].[AllSite], [UserIP].[AllIP]) } ON ROWS
from [AccessAnalysis]
</jp:mondrianQuery>
可以发现,该文件就是Main.java文件的jsp实现方式,可视化展示查询结果:
JPivot还提供了诸如下钻、上卷,图表等功能,比如此时点击AllTime可以观察不同时间(注:我个人的数据库中的部分数据存在修改,与db.sql不同):
此时可以把AllTime关闭,点击AllIP可以擦好看不同IP的访问次数:
再点击AllSite,可以查看哪些IP访问了哪些网站:
点击显示图表,默认展示柱状图:
总结:Mondrian+JPivort是比较强大的ROLAP工具,尤其是JPivot可以以可视化的形式展示结果。本示例只是展示了Mondrian的冰山一角。后续我可能会发布一些细节的功能实现,比如以restful的形式提供功能等。
3.注意事项
3.1 JPivot MDX编辑框中文乱码和数据查询问题
在Mondrian中有两个有关于使用中文会遇到的坑,第一个就是在JPivot的MDX编辑界面时,输入的中文会乱码。这个问题我参考网上的并没有解决。。。
第二个则是我个人遇到的坑。目前互联网上有关Mondrian的连接一般是下面的流程:
package com.mondrian;
import java.io.PrintWriter;
import mondrian.olap.Connection;
import mondrian.olap.DriverManager;
import mondrian.olap.Query;
import mondrian.olap.Result;
public class Main {
public static void main(String[] args) {
String connectString = "Provider=mondrian;Jdbc=jdbc:mysql://127.0.0.1/FoodMart?user=root&password=root;Catalog=FoodMart3.xml;";
Connection connection = DriverManager.getConnection(connectString,null);
String mdx = "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales], [Measures].[Profit]} ON COLUMNS," +
" {[Time].[1997].[Q1]} ON ROWS" +
" FROM [Sales] WHERE [Gender].[Gender].[男]";
Query query = connection.parseQuery(mdx);
Result result = connection.execute(query);
PrintWriter pw = new PrintWriter(System.out);
result.print(pw);
pw.flush();
}
}
FoodMart3.xml是官方提供的一个示例,其文件内容如下:
<?xml version="1.0"?>
<Schema name="FoodMart2">
<Cube name="Sales">
<Table name="sales_fact_1997"/>
<!--维表 性别-->
<Dimension name="Gender" foreignKey="customer_id">
<Hierarchy hasAll="true" allMemberName="All Genders" primaryKey="customer_id">
<Table name="customer"/>
<Level name="Gender" column="gender_zh" uniqueMembers="true"/>
</Hierarchy>
</Dimension>
<!--维表 时间-->
<Dimension name="Time" foreignKey="time_id">
<Hierarchy hasAll="false" primaryKey="time_id">
<Table name="time_by_day"/>
<Level name="Year" column="the_year" uniqueMembers="true"/>
<Level name="Quarter" column="quarter" uniqueMembers="false"/>
<Level name="Month" column="the_month" type="String" uniqueMembers="false"/>
<Level name="Week" column="week_of_year" uniqueMembers="false"/>
<Level name="Day" column="the_day" type="String" uniqueMembers="false"/>
</Hierarchy>
</Dimension>
<!--度量-->
<Measure name="Unit Sales" column="unit_sales" aggregator="sum" formatString="#,###"/>
<Measure name="Store Sales" column="store_sales" aggregator="sum" formatString="#,###.##"/>
<Measure name="Store Cost" column="store_cost" aggregator="sum" formatString="#,###,00"/>
<!--新定义的一个度量 Profit=[Store Sales] - [Store Cost]-->
<CalculatedMember name="Profit" dimension="Measures" formula="[Measures].[Store Sales] - [Measures].[Store Cost]">
<CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
</CalculatedMember>
</Cube>
</Schema>
注:在FoodMart3.xml文件中,我所做的就是在FoodMart数据库中的customer表添加了一个gender_zh字段,其值可能是男或女。
首先是连接mysql数据库和数据立方体的配置文件,然后一个查询的MDX语句,最后则是输出。使用这种方法貌似没有问题,但是,此时输出会返回空:
Axis #0:
{[Gender].[All Genders].[男]}
Axis #1:
{[Measures].[Unit Sales]}
{[Measures].[Store Sales]}
{[Measures].[Profit]}
Axis #2:
{[Time].[1997].[Q1]}
Row #0:
Row #0:
Row #0:
其中的一个主要原因,也就是我遇到的原因,就是在使用jdbc连接mysql数据库的时候,没有指定编码方式,因此connectString应该改为:
String connectString = "Provider=mondrian;Jdbc=jdbc:mysql://127.0.0.1/FoodMart?useUnicode=true&characterEncoding=utf-8&user=root&password=root;Catalog=FoodMart3.xml;";
此时输出正常:
Axis #0:
{[Gender].[All Genders].[男]}
Axis #1:
{[Measures].[Unit Sales]}
{[Measures].[Store Sales]}
{[Measures].[Profit]}
Axis #2:
{[Time].[1997].[Q1]}
Row #0: 33,381
Row #0: 69,830.12
Row #0: $41,899.65
如果是在JPivot中使用的话,则需要在jsp文件的<jp:mondrianQuery>的jdbcUrl添加userEncoding和characterEncoding即可:
<jp:mondrianQuery id="query01" jdbcDriver="com.mysql.jdbc.Driver" jdbcUrl="jdbc:mysql://127.0.0.1/ren_db?useEncoding=true&characterEncoding=utf-8" catalogUri="/WEB-INF/queries/FoodMart3.xml" jdbcUser="root" jdbcPassword="root" connectionPooling="false">
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales], [Measures].[Profit]} ON COLUMNS,
{[Time].[1997].[Q1]} ON ROWS FROM [Sales] WHERE [Gender].[Gender].[男]
</jp:mondrianQuery>
3.2 Mondrian的版本问题
目前官方同时维护的Mondrian版本有3.x、4.x和8.x。在github地址也并没有过多提及相关的区别,在mondrian-rest(Mondrian-rest使用spring boot实现了Mondrian的restful API)中关于Mondrian版本的部分介绍为:
Initial development of mondrian-rest supported version 4.x of the Mondrian library. However, Mondrian 4.x does not appear to be under active development by the core Mondrian maintainers, so we have abandoned it as a dependency. The last version of mondrian-rest that supports version 4.x schemas is version 1.5.0. PRs for the 1.x line of mondrian-rest will be considered, but active development of mondrian-rest by the core committers is focused on Mondrian 8.x (and, at some point, version 9.x). There are significant schema differences between 4.x and 8.x+, however for the most part, the mondrian-rest API is consistent between mondrian-rest v2.x and v1.x.
内容大致是:
Mondrian-rest的初始开发支持Mondrian的4.x版本。但是,mondrian4.x似乎并没有被核心维护人员积极开发,所以我们放弃了它作为一个依赖项。支持版本4.x架构的mondrian-rest的最后一个版本是版本1.5.0。mondrian-rest的积极开发主要集中在Mondrian的8.x版本(在某些情况下,版本9.x)。4.x和8.x+之间存在显著的模式差异,但是在大多数情况下,mondrian-rest api在mondrian-rest v2.x和v1.x之间是一致的。
3.3 可视化
Mondrian官方推荐使用JPivot实现OLAP的可视化,虽然JPivot在交互上较为强大,但是我个人认为它在可视化和使用上要略差,而且JPivot官网的war包很久没有更新了,因此,这里我个人推荐使用Saiku来实现Mondrian的可视化。有关于Saiku的快速上手,我推荐这个资源:saiku2.6解压我的Tomcat资源可以直接运行,以及这个帖子:saiku2.6完整版(运行Tomcat可以直接使用)。俗话说,百闻不如一见,通过这个资源和配套的这个帖子可以快速上手Saiku,在大致了解了saiku的功能后可以考虑是否使用saiku。