Mondrian开源OLAP引擎详解
Mondrian是一个基于Java语言的开源OLAP引擎,它通过MDX语句执行查询,从关系型数据库RDBMS中读取数据,然后经过Java API以多维度的形式展示查询结果。Mondrian是一个OpenSource的基于关系数据库的分析服务器,遵循MDX、XML/A和JOLAP标准。
Mondrian的架构
Mondrian从架构上可以分为四个层次:表现层、计算层、聚合层、存储层。
- 表现层:指最终呈现在用户显示器上的以及与用户之间的交互,有许多方法来展现多维数据,包括数据透视表、饼、柱、线状图。编写jsp文件用于展示它位于展示层由Jpivot提供展示。JPivot 是Mondrian的表现层TagLib,Jpivot完全基于JSP+TagLib;JPivot另外一个可能使人不惯的地方是它完全基于taglib而不是大家熟悉的MVC模式。但它可以很方便的将多维数据展示给最终用户。
- 计算层:分析、验证、执行MDX语句, 先计算坐标轴,然后再计算每个单元格的值,从效率上的考虑,计算层批量从聚合层获取单元格数据集合。
- 聚合层:聚合层中缓存了多维查询结果,即单元格的数据集合,如果计算层所需要的数据不在缓存中,从存储层中进行查询获取数据并缓存。schema文件(关键部分)将存储层的数据仓库转化为一个schema文件,通过schema-workbench或者手写完成,至此就可以通过MDX来对多维数据库进行访问。
- 存储层:采用关系数据库实现,一般采用星型模型构建,提供维表、事实表和聚合表。
系统部署结构上,可以分三层结构分开部署,将表现层部署在一台机器上,计算层和聚合层部署在第二台,存储层部署在第三台。
上图为整体的项目架构,图中所示Mondrian分成了四个大部分Schema manager、Session Manager、Dimension Manager、Aggregate Manager,而实际上各个部分有着更为紧密的联系。对于Dimensional Layer、Star Layer和SQL Layer的划分,更多是处于总体逻辑分层的考虑,具体在源码中,逻辑分层的概念比较模糊。
下面是四个Manager的简介:
- Session Manager:最为重要的一个部分。接受MDX查询、解析MDX,返回结果。
- Schema Manager:与初始化紧密相关。主要是一些重要的数据结构如缓存池的构建以及多维模型的生成。
- Aggregate Manager:实现了对聚集表的管理。主要是对OLAP缓存的管理,属于性能优化的部分。
- Dimension Manager:维度的管理。实现多维模型中维度和关系数据库表中列的映射,在Schema Manager也有部分功能处理这些映射。
存储、聚合
根据OLAP服务器数据存储技术,可以分为ROLAP和MOLAP,Mondrian采用ROLAP技术。在ROLAP的多维模型组织和存储数据中,比较常用的方式是星型模式,由一个事实表和一组维度表组成。维度必须预先确定,可以是一般的星型架构,也可以是比较特殊的父子架构、雪花架构等。在ORACLE数据库中,我们能够按照维度进行预先的统计、分类、排序,创建大量的实体化视图。对于没有实体化视图类似功能的数据库,我们也可以创建大量的临时聚合表,这样当用户进行比较高级的分析的时候,不用访问数据量庞大的基础事实表,只需要在我们已经形成的实体化视图或聚合表上作进一步的聚合就可以了,这样能够大大提高查询分析的效率,并且减少占用的系统资源。
在使用聚合表方面有一个关键的技术称之为”聚合感知”技术,只有OLAP引擎必须能够根据多维查询中的维度信息,从适当的聚合表中提取数据,否则聚合表形同虚设。另外在多维模型设计中,设计正确的聚合表也是很关键的。不恰当的设计导致将聚合表的不能满足多维查询的需求,每次从事实表取数据,开销是非常大的。Mondrian采用关系数据库存储事实表,其本身不具有”聚合感知”的技术,当需要从比较明细的事实表中汇总数据时,性能就比较差了。但是如果关系数据库能够支持实体化视图或其他聚合技术,那么就可以利用关系数据库的技术透明的提高系统性能。可以利用Oracle实体化视图来提高Mondrian的效率。
下图描述了一个数据库的结构。该数据库中共有五张表,分别是Sales表,Customer表,Time表,Product表和Mfr表。这个数据库的作用是存储每一笔交易:包括这笔交易发生在什么时间,交易的产品类型,进行交易的客户信息,交易方式,交易了多少件产品以及成交金额是多少。
星型模型中有一张事实表(Sales),两个度量列(units和dollars),四个维度表(Product, Mfr, Customer, Time)。在这个星型模型的最顶层,我们创建了以下多维模型:
- [Sales]立方体包含[Unit sales]和[Dollar sales]两个度量值;
- [Product]维度包含[All Products],[Manufacturer],[Brand],[Prodid]四个级别;
- [Time]维度包含[All Time],[Year],[Quarter],[Month],[Day]五个级别;
- [Customer]维度包含[All Customers],[State],[City],[Custid]四个级别;
- [Payment Method]维度包含[All Payment Methods],[Payment Method]两个级别。
其中,大部分维度都有一个对应的维度表,除了两个地方:[Product]维度是一个雪花维度,它会把Product和Mfr两张表展开;[Payment Method]维度是一个退化的维度,直接使用事实表中的payment列作为维度属性,因此不需要一个单独的维表。
假设现在我们要对交易做一些统计,例如,某一件特定产品在某一个时间段内以某种特定方式总共卖出多少件或多少钱,这时成交产品数和成交金额是我们最终关注的内容,其他的因素例如时间、产品、方式等都只是对我们最终关注内容进行统计的限制条件。
在上面的例子中,限制条件有时间、产品类型、用户类型和交易方式,有时我们并不需要同时使用所有的限制条件,例如,当我们只想知道指定产品的成交总金额时,那么除了产品类型之外其他三个限制条件都是多余的,而在查询时,需要在整个事实表中执行查询,找出产品类型为指定类型的所有产品然后再做统计,为了提高查询效率,我们可以新建一张表,这张表按照产品类型把事实表中的行合并到一起,合并的方式是抛弃其他维,把度量值按特定的方式(max,min,sum,count或avg)整合到一起。这种表被叫做聚合表(Aggregate Table)。
Mondrian缓存控制
为了提高海量数据下的查询响应速度,Mondrian自动将首次查询的结果缓存到内存中,之后的查询如果命中缓存内容,则不再访问数据库。这种实现方式有点自不必说,但是在实现实时OLAP时会存在问题,实时OLAP中数据变化频繁导致缓存中的数据不是最新的。
- 缓存控制接口:为了做到不重启OLAP Server也能更新缓存,Mondrian提供了一系列的刷新缓存的接口,支持指定清除指定schema的元数据缓存、查询结果缓存;清除动作可以是全部清除 也可以是 部分清除(可以指定清除某个维度下某级别成员的相关内容)。
- 数据变化监听: Mondrian提供了缓存控制接口(被动响应),但对于实现我们的目标“实时OLAP”来说我们就需要自己实现一个数据变更监听的模块,来监听数据变化,一旦数据有变化就发起变更事件,更新Mondrian引擎的缓存。
目前初步考虑实现方案为ETL工具在数据处理结束后通知OLAP引擎。引擎收到数据变更通知后做清理缓存的动作。
Mondrian Scheme详解
Mondrian通过Schema来定义一个多维数据库,它是一个逻辑概念上的模型,其中包含Cube(立方体)、Dimension(维度)、Hierarchy(层次)、Level(级别)、Measure(度量),这些被映射到数据库物理模型。Mondrian中Schema是以XML文件的形式定义的。
- Cube(立方体)是一系列Dimension和Measure的集合区域,它们共用一个事实表。
- Dimension(维度)是一个Hierarchy的集合,维度一般有其相对应的维度表,它由Hierarchy(层次)组成,而Hierarchy(层次)又是由组成Level(级别)的。
- Hierarchy(层次)是指定维度的层级关系的,如果没有指定,默认Hierarchy里面装的是来自立方体中的真实表。
- Level(级别)是Hierarchy的组成部分,使用它可以构成一个结构树,Level的先后顺序决定了Level在结构树上的位置,最顶层的 Level 位于树的第一级,依次类推。
- Measure(度量)是我们要进行度量计算的数值,支持的操作有sum、count、avg、distinct-count、max、min等。
Schema
Schema 定义了一个多维数据库。包含了一个逻辑模型,而这个逻辑模型的目的是为了书写 MDX 语言的查询语句。这个逻辑模型实际上提供了这几个概念:
- Cubes: 立方体
- Dimensions: 维度
- Hierarchies: 层次
- Levels: 级别
- Members: 成员
而一个schema 文件就是编辑这个 schema 的一个xml 文件。在这个文件中形成逻辑模型和数据库物理模型的对应。
Cube
一个 Cube 是一系列维度 (Dimension) 和度量 (Measure) 的集合区域。在 Cube 中, Dimension 和 Measure 的共同地方就是共用一个事实表。 Cube 中的有以下几个属性:
- name: Cube 的名字。
- caption: 标题 , 在表示层显示的。
- cache: 是否对 Cube 对应的实表用 mondrian 进行存储 , 默认为 true。
- enabled: 是布尔型的 , 如果是被激活 ,Cubes 就执行 , 否则就不予理睬,默认为 true。
- Cube 里面有一个全局的标签定义了所用的事实表的表名。
Dimension
他是一个层次( Hierarchies )的集合 , 维度一般有其相对应的维度表 . 他的组成是由层次(Hierarchies)而层次(Hierarchies)又是有级别(Level)组成 . 其属性如下:
- name: Dimension 的名称。
- type: 类型,有两个可选的类型: StandarDimension 和 TimeDimension ,默认为StandardDimension。
- caption: 标题 , 在表示层显示的UsagePrefix加前缀 , 消除歧义。
- foreignKey: 外键,对应事实表中的一个列,它通过 <Hierarchy> 元素中的主键属性连接起来。
Hierarchy
你一定要指定其中的各种关系,如果没有指定,就默认 Hierarchy 里面装的是来自立方体中的真实表 . 属性如下:
- name: Hierarchy 的名称,该值可以为空,为空时表示 Hirearchy 的名字和 Dimension 的名字相同。当一个 Dimension 有多个 Hierarchy时,注意 name 值要唯一。
- hasAll: 布尔型的 , 决定是否包含全部的成员 member。
- allMemberName: 所有成员的名字 , 也就是总的标题 , 例如: allMemberName= “全部产品”。
- allLevelName: 所有级别的名字,它会覆盖其下所有的 Member 的 name 和所有的 Level 的 name 属性的值。
- allMemberCaption: 例如 : allMemberCaption= “全部产品”这个是在表示层显示的内容。
- PrimaryKey: 通过主键来确定成员,该主键指的是成员表中的主键,该主键同时要与 Dimension 里设置的 foreignKey 属性对应的字段形成外键对应关系。
- primaryKeyTable: 如果成员表不只一个,而是多个表通过 join 关系形成的,那么就要通过这个属性来指明 join 的这些表中,哪一个与Dimension 里设置的foreignKey 属性形成外键关系。通过该属性来指明主表。
- caption: 标题 , 在表示层显示的。
- defaultMember
- memberReaderClass 设定一个成员读取器,默认情况下 Hierarchy 都是从关系型数据库里读取的,如果你的数据不在 RDBMS 里面的话,你可以通过自定义一个member reader 来表现一个 Hierarchy 。
Level
级别 , 他是组成 Hierarchy 的部分。属性很多,并且是 schema 编写的关键,使用它可以构成一个结构树, Level 的先后顺序决定了 Level在这棵树上的的位置,最顶层的 Level 位于树的第一级,依次类推。 Level 的属性如下:
- name: 名称
- table: 该 Level 要使用的表名
- column: 用上面指定的表中某一列作为该 Level 的关键字
- nameColumn: 用来显示的时候使用,如果不定义,那么就采用上面的 column 的值来进行显示。
- oridinalColumn: 定义该 Level 上的成员的显示顺序,如果不指定,那么采用 column 的值。
- parentColumn: 在一个有父 – 子关系的 Hierarchy 当中,当前 Level 引用的是其父成员的列名。好比是一张部门表,在一张表里表现部门的上下级关系,一个是主键,肯定还有一个字段为连接到该主键的外键的列名,这里的 parentColumn 指的就是这个列名。
- nullParentValue: 如果当前的 Level 是有上下级关系(设置了 parentColumn 属性),如果该 Level 又处于顶级,我们需要将顶级的数据取出来,这里指的是位于顶级的父成员的值,有些数据库不支持 null, 那么也可以使用0或-1 等,这就表示顶级的成员的父 ID 为0 或为-1 。
- type: 数据类型,默认值为 string 。当然还可以是 Numeric 、 Integer 、 Boolean 、 Date 等。
- uniqueMembers: 该属性用于优化产生的 SQL ,如果你知道这个级别和其父级别交叉后的值或者是维度表中给定的级别所有的值是唯一的,那么就可以设置该值为 true ,否则为 false 。
- levelType: 该 Level 的类型,默认为 regular (正常的),如果你在其 Dimension 属性 type 里选择了 TimeDimension 那么这里就可以选择 TimeYears 、 TimeQuarters 、 TimeMonth 、 TimeWeeds 、 TimeDays 。
- hideMemberIf: 在什么时候不隐藏该成员,可选的值有三个: Never 、 IfBlankName 、 IfParentName
- approxRowCount: 该属性可以用来提高性能,可以通过指定一个数值以减少判断级别、层次、维度基数的时间,该属性在通过使用 XMLA 连接Mondrian 很有用处。
- caption: 标题 , 在表示层显示的。
- captionColumn: 用来显示标题的列。
- formatter: 该属性定义了 Member.getCaption() 方法返回的动作值,这里需要是一个实现了 mondrian.olap.MemberFormatter 接口的类,用来对Caption地值进行格式化。
Join
对于一个 Hierarchy 来说,有两种方式为其指定:一种是直接通过一个 Table 标签指定;一种是通过 Join 将若干张表连接起来指定。一旦采用 Join 的话,那么就要在 Hierarchy 里的 primaryKeyTable 属性指定主表。
Measure
Measure 就是我们要计算的数值,操作的核心。它的属性如下:
- name: 名称。
- aggregator: 要采用的计算函数。
- column: 要计算的列名。
- formatString: 计算结果的显示格式。
- visible: 是否可见。
- datatype: 数据类型,默认为 Numeric
- formatter: 采用类来对该 Measure 的值进行格式,具体参考 Level 的 formatter 属性。
- caption: 标题,用来显示时使用。
概括总结一下:在多维分析中,关注的内容通常被称为度量(Measure),而把限制条件称为维度(Dimension)。多维分析就是对同时满足多种限制条件的所有度量值做汇总统计。包含度量值的表被称为事实表(Fact Table),描述维度具体信息的表被称为维表(Dimension Table),同时有一点需要注意:并不是所有的维度都要有维表,对于取值简单的维度,可以直接使用事实表中的一列作为维度展示。
参考链接: