OLAP(OnLine Analytical Processing)是一种数据处理技术,OLAP系统通常被用来进行面向业务主题的数据分析,在大数据量情况快速响应复杂查询以及灵活的进行随机性探索性分析是OLAP系统的核心能力。
在OLAP系统中常见的数据模型为以下四种结构:
- 星型表结构(Star Schema)
- 雪花型表结构(Snowflake Schema)
- 宽表结构(Wide Table)
- 数据立方体结构(Cube)
这几种常见的结构都可以在逻辑上被理解成是多维空间中的多维立方体。
接下来介绍一个在后续的实践环节中将要用到的数据模型——Andes Online Store,这是一个电商的销售数据集合,它关联了六个维度:日期(Date)、商品(Goods)、地区(Region)、支付方式(Payment Methods)、客户类型(Customer Types)、销售渠道(Sales Channels),它有两个度量:销售额(Sales)和销售数量(Sales Count)。
对于这个数据模型,不同的人会有不同的分析需求,例如:
- 分公司负责人重点关注所在地区的销售数据,公司决策层更关心整体的销售数据,整体数据是由各个地区的明细数据聚合而得出,不同的人关注的数据粒度不同;
- 采购负责人会比较关心商品维度上的数据变化,营销人员关心的是客户或销售渠道维度上的数据变化,而财务人员可能会对日期维度上的数据变化更感兴趣,不同的人关注的业务角度不同。
即席查询(Ad Hoc)是用户根据自己的需求,灵活的选择查询条件,OLAP系统根据用户输入的查询条件实时返回查询结果。OLAP的即席查询与普通查询的不同之处就是很难对前者进行预先的优化,因为即席查询所响应的大都是随机性很强的查询请求。一个OLAP系统的即席查询能力越强,其应对不同用户的随机性和探索性分析的能力就越强。
EuclidOLAP是一个适用于即席查询场景的开源OLAP引擎,它使用C语言开发,支持单机运行和分布式部署。
EuclidOLAP通过以下特征来提供对即席查询场景的支持能力:
- 实时聚合 — 对任何维度的粗粒度数据进行实时聚合运算,无需进行预计算处理;
- 内存模式 — 数据被加载到内存中的弹性索引结构中,一亿数据量只需要3G内存,32G内存便可支持十亿级数据的实时分析;
- 多维模型 — EuclidOLAP采用Cube结构作为语义层模型,其完全可以等同于逻辑多维模型,并且更加贴近现实业务;
- 复杂查询 — 使用类SQL语言MDX,MDX在语法结构上与SQL非常类似,但提供了比SQL更加强大的应对复杂查询的能力;
- 关联查询 — 支持跨Cube查询,这类似于关系数据库中的Join操作,但EuclidOLAP跨Cube查询不会导致性能明显下降。
下图是EuclidOLAP单机运行时的架构图。
- 磁盘中数据模型的元数据部分被加载到内存中形成概要文件(Profile),profile用来描述维度信息;
- 度量数据被加载到内存中的弹性索引(Elastic Index)中(如果你了解Oracle Essbase或IBM Cogons,那么可以将弹性索引在逻辑上理解为动态的稀疏维索引和密集维数据块),弹性索引是EuclidOLAP实现高效实时聚合的关键;
- Profile和弹性索引表示了一个Cube的物理存储结构,它完全等同于MDX语义层的逻辑多维模型;
- MDX解析器接收一个MDX查询请求并将其解析为一个多维查询抽象语法树(AST);
- 对AST的进一步处理分为逻辑运算(Logical operation)和聚合运算(Aggregate operation)两部分;
- 逻辑运算将通过概要文件确定此次查询请求的数据范围,同时处理函数解析、数学运算、布尔操作等一系列与复杂逻辑相关的步骤;
- 聚合运算通过逻辑运算所确定的范围通过弹性索引进行实时汇总运算;
- 将查询结果以多维结果集(通常是一维或二维)的形式返回。
在分布式部署时,EuclidOLAP集群分为负责逻辑运算的master节点和负责聚合运算的worker节点。一个数据集市模型的维度元数据被加载到master节点中形成概要文件,度量数据以分片的形式存储在各个worker节点中,如下图所示。
接下来进入实践环节,我们先使用Docker运行一个EuclidOLAP服务,然后模拟各种用户根据自身的需求面向业务模型进行随机的探索式的数据分析。
在命令行窗口中执行下面的指令,运行一个EuclidOLAP服务:
docker run -d -p 8760:8760 --name euclidolap euclidolap/euclidolap:v0.1.7
文章开始部分已经简单介绍了用于实践环节的数据模型示例 — Andes Online Store,这里我们再回顾一下这个demo。
Date、Goods和Region三个维度具有多层级结构,分别如下:
- Date:Root(默认全部汇总)> Year > Quarter > Month > Day
- Goods:Root(默认全部汇总)> 商品大类 > 分类 > 具体商品
- Region:Root(默认全部汇总)> 洲 > 国家
Payment Methods、Customer Types和Sales Channels三个维度是单层级结构,如下:
- Payment Methods:Root(默认全部汇总)> 支付方式
- Customer Types:Root(默认全部汇总)> 客户类型
- Sales Channels:Root(默认全部汇总)> 销售渠道
接下来执行一个简单的查询,验证EuclidOLAP是否启动成功。
执行以下命令进入Docker容器:
docker exec -it euclidolap /bin/bash
在容器中执行olap客户端命令行工具:
./olap-cli
在olap客户端中执行下面的MDX查询语句:
select
[Date].[2022].[Q4] on rows,
[Goods].[Foodstuff] on columns
from [Andes Online Store];
如果你的屏幕上显示出了相同的结果表示EuclidOLAP运行正常。
后面将通过一系列的随机查询来演示EuclidOLAP的即席查询能力。
通过任意维度分析数据
下面的3个MDX语句分别从支付方式、客户类型和销售渠道三个维度查看相应销售额和销售数量数据。对于其他没有被显式指定的维度,EuclidOLAP将自动进行汇总。执行下面三个MDX并获得查询结果。
支付方式
select
{[Payment Methods].[Credit card], [Payment Methods].[PayPal]} on rows,
{[Measures].Sales, [Measures].[Sales Count]} on columns
from [Andes Online Store]
where ([Date].[2022].[Q4].[M12]);
客户类型
select
{[Customer Types].[Bargain hunters], [Customer Types].[New customers]} on rows,
{[Measures].Sales, [Measures].[Sales Count]} on columns
from [Andes Online Store]
where ([Date].[2022].[Q4].[M12].[31], [Goods].[Foodstuff].[Meat]);
销售渠道
select
{[Sales Channels].[Wholesale], [Sales Channels].[Direct sales]} on rows,
{[Measures].Sales, [Measures].[Sales Count]} on columns
from [Andes Online Store]
where ([Date].[2022].[Q4].[M12].[31], [Goods].[Foodstuff].[Meat]);
通过不同的数据粒度进行分析
下面的3个MDX语句分别从商品维度的具体商品、商品分类和商品大类三个粒度进行查询。
按具体商品查询2022年1季度销售额
select
[Date].[2022].[Q1] on rows,
{[Goods].[Foodstuff].[Drink].[Milk], [Goods].[Foodstuff].[Drink].[Tea]} on columns
from [Andes Online Store];
按商品分类查询2022年1季度销售额
select
[Date].[2022].[Q1] on rows,
{[Goods].[Foodstuff].[Drink], [Goods].[Foodstuff].[Meat]} on columns
from [Andes Online Store];
按商品大类查询2022整年的销售额
select
[Date].[2022] on rows,
{[Goods].[Foodstuff], [Goods].[Clothing]} on columns
from [Andes Online Store];
可以在查询时将任何维度放置在任何位置
下面这条MDX语句返回的结果是日期与商品维度的交叉透视表,日期与商品被分别放置在列和行上:
select
{[Date].[2021], [Date].[2022]} on rows,
{[Goods].[Foodstuff], [Goods].[Clothing]} on columns
from [Andes Online Store];
稍微修改一下上面的MDX就可以将日期与商品维度的位置互换:
select
{[Date].[2021], [Date].[2022]} on columns,
{[Goods].[Foodstuff], [Goods].[Clothing]} on rows
from [Andes Online Store];
可以对维度进行交叉组合
下面这个MDX语句查询了各种客户类型与支付方式对应的销售数据,客户类型和支付方式维度被同时放置在行位置并进行交叉组合。
select
Crossjoin(
{[Customer Types].[Bargain hunters], [Customer Types].[New customers]},
{[Payment Methods].[Credit card], [Payment Methods].[PayPal]}
) on rows,
[Measures].Members on columns
from [Andes Online Store]
where ([Date].[2022].[Q4].[M12]);
数学运算
下面的MDX定义了一个新的度量Formula_Measure,它的值是对销售数量度量进行数学运算而得出的:
with
member [Measures].[Formula_Measure]
as (([Measures].Sales + 1000) / 20 - 33000) * 0.01
select
[Date].[2022].[Q1] on rows,
{[Measures].Sales, [Measures].[Formula_Measure]} on columns
from [Andes Online Store];
跨业务模型分析
下面这个MDX通过LookupCube函数将两个Cube关联起来进行分析,这类似于关系数据库中的Join操作,但与Join操作不同之处在于EuclidOLAP的跨模型分析不会造成查询性能的显著下降。
with
member [Measures].other_sales as LookupCube("Sahara Online Store", [Measures].Sales)
select
[Date].[2022].[Q1] on rows,
{[Measures].Sales, [Measures].[other_sales]} on columns
from [Andes Online Store];
函数
下面3个MDX查询分别使用了成员函数(Member Function)、集合函数(Set Function)和数值函数(Numeric Function)。
使用Parent函数查询澳大利亚所在洲的2022年的销售数据:
select
[Region].[Oceania].[Australia].Parent() on rows,
([Date].[2022], [Measures].Sales) on columns
from [Andes Online Store];
使用Children函数查询球类商品分类下的所有具体商品的销售数据:
select
Children([Goods].[Motion].[Ball]) on rows,
[Measures].Members on columns
from [Andes Online Store];
使用Avg函数查询2022年各个季度销售额的平均值:
with member [Measures].AVG_SALES
as Avg(Date.currentMember().Children(), [Measures].Sales)
select
Date.[2022] on rows,
[Measures].AVG_SALES on columns
from [Andes Online Store];
逻辑运算
下面的MDX使用了逻辑函数——IsLeaf,当商品维度成员为明细成员时显示其对应的销售额数据,对于非明细成员则显示一个自定义字符串。
with member [Measures].mix_num_str
as Iif(IsLeaf(CurrentMember(Date)), [Measures].Sales, "NO LEAF MEMBER")
select
{Date.[2022], Date.[2022].Q1.M1.[10]} on rows,
[Measures].mix_num_str on columns
from [Andes Online Store];
计算公式维度成员
下面的MDX使用了一个自定义的度量维度成员——Proportion,它的值是通过当前商品维度成员的值与其父级成员的值动态计算得出,可以使用它来查看各种具体商品占其所属商品分类的百分比。
with member [Measures].Proportion
as (Goods.CurrentMember, [Measures].Sales) / (Goods.CurrentMember.Parent, [Measures].Sales)
select
[Goods].[Foodstuff].[Fruits].Children() on rows,
[Measures].Proportion on columns
from [Andes Online Store];
自定义集合
下面的MDX在定义查询条件之前设置了两个自定义集合——东亚国家和北美国家,这样在查询条件中就可以直接使用自定义集合的名称。
with
set [East Asian countries] as {[Region].[Asia].[China], [Region].[Asia].[Japan], [Region].[Asia].[India]}
set [North American countries] as {[Region].[North America].[Canada], [Region].[North America].[United States of America]}
select
Union([East Asian countries], [North American countries]) on rows,
[Measures].Sales on columns
from [Andes Online Store];