数据仓库, OLAP 与 OLTP ,维度和度量,事实表和维度表。星型模型和雪花模型。
这是商业智能( BI )的核心部分,主要是将不同数据源的数据整合到一起,通过多维分析为企业提供决 策支持、报表生成等。
数据仓库和数据库主要区别:用途不同。
数据库 | 数据仓库 |
---|---|
面向事务 存储在线的业务数据,对上层业务改变作出实时反映,遵循三范式设计。 |
面向分析 历史数据,主要为企业决策提供支持,数据可能存在大量冗余,但是利于多个维度查询,为决策者提供更多观察视角。 |
一般来说,在传统 BI 领域里,数据仓库的数据同样是存储在 MySQL 这样的数据库中。大数据领域最常 用的数据仓库就是 Hive ,我们要学习的 Kylin 也是以 Hive 作为默认的数据源的。
OLAP
和OLTP
OLAP(Online Analytical Process
),联机分析处理,大量历史数据为基础,配合时间点的差异,以多维度的方式分析数据, 一般带有主观的查询需求,多应用在数据仓库。
OLTP(Online Transaction Process
),联机事务处理,侧重于数据库的增删查改等常用业务操作。
这两个是数据分析领域中两个常用的概念。维度( Dimension )简单来说就是你观察数据的角度,也就 是数据记录的一个属性例如时间、地点等。度量( Measure )就是基于数据所计算出来的考量值,通常 就是一个数据比如总销售额,不同的用户数量。我们就是从不同的维度来审查度量值,以便我们分析找 出其中的变化规律。
对应我们的 SQL 查询, group by 的属性通常就是我们考量的维度,所计算出来的比如 sum (字段)就 是我们需要的度量。
对于 OLAP 系统(数据仓库)就是我们在统计的时候将维度相同的记录聚合在一起,然后应用聚合函数做各种各样的聚合计算。看一下年度销售额是否在正常范围,和往年的同比增幅,都可以一目了然。
Cube
和cuboid
我们在确定好了维度和度量之后,我们根据定义好的维度和度量,就可以构建cube
(立方体)。也就是所谓的预计算,对原始数据建立的多维度索引。
给定一个数据模型,我们可以对其上的所有维度进行组合。对于N个维度来说,组合的所有可能性共有2^N
种。对于每一种维度的组合,将度量做聚合运算,然后将运算的结果保存为一个物化视图,称为Cuboid
。所有维度组合的Cuboid
作为一个整体,被称为Cube。
所以简单来说,一个 Cube
就是许多按维度聚合的物化视图的集合。
下面来列举一个具体的例子。假定有一个电商的销售数据集,其中维度包括
时间(Time
)、商品(Item
)、地点(Location
)和供应商(Supplier
),
度量为销售额(GMV
)。那么所有维度的组合就有2^4 =16
即16种(如下图所示),比如一维度(1D
)的组合有[Time]、[Item]、[Location]、[Supplier]
4种; 二维度(2D
)的组合有[Time,Item]、[Time,Location]、[Time、Supplier]、 [Item,Location]、[Item,Supplier]、[Location,Supplier]
6种;三维度(3D
)的 组合也有4种;最后零维度(0D
)和四维度(4D
)的组合各有1种,总共就有 16种组合。下图为kylin
官方图例。
通过维度聚合销售额来计算cuboid
,通过sql
语句来表达这样的计算Cuboid[Time,Location]
,如下:
select Time,Location,SUM(GMV) as GMV from Sales group by Time,Location;
将计算的结构保存为物化视图,即所有的Cuboid
物化视图的总称就是Cube
。
Cube Segment
见名知意代表的是元数据中的某一个片段计算出的cube
数据。通常数据仓库中的数据量会随着时间的增长而增长,而Cube Segment
也是按照时间顺序来构建的。
事实表(Fact Table
)是指存储有事实记录的表,如系统日志、销售记录等;事实表的记录在不断地动态增长,所以它的体积通常远大于其他表。
维度表(Dimension Table
)或维表,有时也称查找表(Lookup Table
),是与事实表相对应的一种表;它保存了维度的属性值,可以跟事实表做关联;相当于将事实表上经常重复出现的属性抽取、规范出来用一张表进行管理。
常见的维度表有:日期表(存储与日期对应的周、月、季度等的属性)、地点表(包含国家、省/州、城市等属性)等。使用维度表有诸多好处,具体如下:
缩小了事实表的大小。
便于维度的管理和维护,增加、删除和修改维度的属性,不必对事实表的大量记录进行改动。
维度表可以为多个事实表重用,以减少重复工作。
star schema
)星型模型就是一张事实表,以及零个或多个维度表;事实表与维度表通过主键外键相关联,维度表之间没有关联,就像很多星星围绕在一个恒星周围,故取名为星形模型。
snowFlake schema
)将星形模型中的某些维表抽取成更细粒度的维表,然后让维表之间也进行关联,这种形状酷似雪花的的模型称为雪花模型。
星型模型因为数据的冗余所以很多统计查询不需要做外部的连接,因此一般情况下效率比雪花型模型要 高。星型结构不用考虑很多正规化的因素,设计与实现都比较简单。
雪花型模型由于去除了冗余,有些统计就需要通过表的联接才能产生,所以效率不一定有星型模型高。 正规化也是一种比较复杂的过程,相应的数据库结构设计、数据的 ETL 、以及后期的维护都要复杂一 些。因此在冗余可以接受的前提下,实际运用中星型模型使用更多,也更有效率。
Apache Kylin
(Extreme OLAP Engine for Big Data
)是一个开源的分布式分析引擎,为Hadoop
等大型分布式数据平台之上的超大规模数据集通过标准 SQL
查询及多维分析(OLAP
)功能,提供亚秒级的交互式分析能力。
Apache Kylin
,中文名麒麟,是Hadoop
动物园中的重要成员。Apache Kylin
是一个开源的分布式分析引擎,最初由eBay
开发贡献至开源社区。它提供Hadoop
之上的SQL
查询接口及多维分析(OLAP
)能力以支持大规模数据,能够处理TB
乃至PB
级别的分析任务,能够在亚秒级查询巨大的Hive
表,并支持高并发。
Apache Kylin
于2014年10月在github
开源,并很快在2014年11月加入Apache
孵化器,于 2015年11月正式毕业成为Apache
顶级项目,也成为首个完全由中国团队设计开发的 Apache
顶级项目。于2016年3月,Apache Kylin
核心开发成员创建了Kyligence
公司,力求更好地推动项目和社区的快速发展。
在大数据的背景下,Hadoop
的出现解决了数据存储问题,但如何对海量数据进行 OLAP
查询,却一直令人十分头疼。企业中大数据查询大致分为两种:即席查询和定制查询。
Hive
、SparkSQL
等OLAP
引擎,虽然在很大程度上降低了数据分析的难度,但它们都只适用于即席查询的场景。它们的优点是查询灵活,但是随着数据量和计算复杂度的增长,响应时间不能得到保证。
多数情况下是对用户的操作做出实时反应,Hive
等查询引擎很难满足实时查询,一般只能对数据仓库中的数据进行提前计算,然后将结果存入Mysql
等关系型数据库,最后提供给用户进行查询。
在上述背景下,Apache Kylin
应运而生。不同于大规模并行处理Hive
等架构,Apache Kylin
采用" 预计算"的模式,用户只需要提前定义好查询维度,Kylin
将帮助我们进行计算,并将结果存储到HBase
中,为海量数据的查询和分析提供亚秒级返回,是一种典型的空间换时间的解决方案。
Apache Kylin
的出现不仅很好地解决了海量数据快速查询的问题,也避免了手动开发和维护提前计算程序带来的一系列麻烦。
Kylin
的工作原理 上面章节所描述的对数据模型做Cube
预计算就是Kylin
的工作原理,典型的空间换时间的例子。利用cube
计算的结构加速我们的查询。具体过程如下。
指定数据模型,定义维度和度量。
预计算Cube
,计算所有Cuboid
并保存为物化视图。
执行查询时,读取Cuboid
,运算,产生查询结果。
Kylin
的查询过程不会扫描原始记录,而是通过预计算预先完成表的关联、聚合等复杂运算,并利用预计算的结果来执行查询,因此相比非预计算的查询技术,其速度一般要快一到两个数量级,并且这点在超大的数据集上优势更明显。当数据集达到千亿乃至万亿级别时,Kylin
的速度甚至可以超越其他非预计算技术1000倍以上。
总结:
Kylin
的核心思想是Cube
预计算,理论基础是空间换时间,把高复杂度的聚合运算、多表连接等操作转换成对预计算结果的查询。
Kylin
技术架构Kylin
系统主要分为在线查询和离线构建两个部分,(第一张图中)橙色线代表在线,灰色线代表离线。
Kylin
是一个开源的分布式分析引擎,提供一个标准的sql
接口,给我们多维分析(OLAP
)提供帮助。你可以把kylin
理解为一个数据仓库。对比之前的hive
,想要一些计算结果我们可能会写一些脚本实现将结果集算好,但是对应业务复杂,维度变化更多的情况,你用shell
脚本就不好控制了。Kylin
就是按照你想要的维度给你全部构建好(即说白了你想要怎样的维度组合数据我就帮你算好)。
用户可以从上方查询系统发送SQL
进行查询分析。Kylin
提供了各种Rest API
、JDBC/ODBC
接口。无论从哪个接口进入,SQL
最终都会来到Rest
服务层,再转交给查询引擎进行处理。这里需要注意的是,SQL
语句是基于数据源的关系模型书写的,而不是Cube
。
Kylin
在设计时刻意对查询用户屏蔽了Cube
的概念,分析师只需要理解简单的关系模型就可以使用Kylin
,没有额外的学习门槛,传统的SQL
应用也很容易迁移。查询引擎解析SQL
,生成基于关系表的逻辑执行计划,然后将其转译为基于Cube
的物理执行计划,最后查询预计算生成的Cube
并产生结果。整个过程不会访问原始数据源。
注意: 对于查询引擎下方的路由选择,在设计者曾考虑过将Kylin
不能执行的查询引导去Hive
中继续执行,但在实践后发现Hive
与Kylin
的速度差异过大,导致用户无法对查询的速度有一致的期望,很可能大多数查询几秒内就返回结果了,而有些查询则要等几分钟到几十分钟,因此体验非常糟糕。最后这个路由功能在发行版中默认关闭,因此在图中是用虚线表示的。
看图,左侧为数据来源,消息队列、hive
等拿到数据之后,通过kylin
处理,将hbase
作为存储介质,满足一定的实时性要求(Hbase
中的每行记录的Rowkey
由dimension
组成,measure
会保存在column family
中。为了减小存储代价,这里会对dimension
和measure
进行编码。查询阶段,利用HBase
列存储的特性就可以保证Kylin
有良好的快速响应和高并发。Kylin
在中间作为媒介,提供rest api
使用以及jdbc
接口供BI
软件做报表的支撑(拓展软件:tableau,superset
)。
总结:hive
查询时间随着数据量的增长而线性增长,kylin
使用预计算技术打破了这一点,kylin
在数据集规模上的局限性主要取决于维度的个数和基数,而不是数据集的大小,所以Kylin
能更好地支持海量数据集的查询。而也正是预计算技术,kylin
的查询速度非常快,亚秒级响应。
kylin
主要特点 Apache Kylin
的主要特点包括支持SQL
接口、支持超大数据集、亚秒级响应、可伸缩性、高吞吐率、BI
工具集成等。
SQL
接口 Apache Kylin
以标准SQL
作为对外服务的主要接口:Kylin
使用的查询模型是数据源中的关系模型表,一般而言,也就是指Hive
表。终端用户只需要像原来查询Hive表一样编写SQL
,就可以无缝地切换到Kylin
,几乎不需要额外的学习,甚至原本的Hive
查询也因为与SQL
同源,大多都无须修改就能直接在Kylin
上运行。
Apache Kylin
对大数据的支撑能力可能是目前所有技术中最为领先的。早在2015年eBay
的生产环境中Kylin
就能支持百亿记录的秒级查询,之后在移动的应用场景下又有了千亿记录秒级查询的案例。这些都是实际场景的应用,而非实验室中的理论数据。
因为使用了Cube
预计算技术,在理论上,Kylin
可以支撑的数据集大小没有上限,仅受限于存储系统和分布式计算系统的承载能力,并且查询速度不会随数据集的增大而减慢。Kylin
在数据集规模上的局限性主要在于维度的个数和基数。它们一般由数据模型来决定,不会随着数据规模的增长而线性增长,这也意味着Kylin
对未来数据的增长有着更强的适应能力。
Apache Kylin
拥有优异的查询响应速度,这点得益于预计算,很多复杂的计算,比如连接、聚合,在离线的预计算过程中就已经完成,这大大降低了查询时刻所需要的计算量,提高了响应速度。根据可查询到的公开资料可以得知,Apache Kylin
在某生产环境中90%的查询可以在3s内返回结果。这并不是说一小部分SQL
相当快,而是在数万种不同SQL
的真实生产系统中,绝大部分的查询都非常迅速;在另外一个真实的案例中,对1000多亿条数据构建了立方体,90%的查询性能都在1.18s以内,可见Kylin
在超大规模数据集上表现优异。
BI
及可视化工具集成Apache Kylin
提供了丰富的API
,以与现有的BI
工具集成,具体包括如下内容。
ODBC
接口:与Tableau
、Excel
、Power BI
等工具集成。JDBC
接口:与Saiku
、BIRT
等Java
工具集成。Rest API**
:与JavaScript
、Web
网页集成。 分析师可以沿用他们最熟悉的BI
工具与Kylin
一同工作,或者在开放的API
上做二次开发和深度定制。
传统技术,如大规模并行计算和列式存储的查询速度都在O(N)
级别,与数据规模增长呈线性关系。
如果数据规模增长10倍,那么O(N)
的查询速度就会下降到十分之一,无法满足日益增长的数据需求。依靠Apache Kylin
,我们不用再担心查询速度会随着数据量的增长而减慢。
Kylin
集群安装部署Kylin
的使用 在我们本地机器上有一张 kylin_sale 表.
project
第一步:在kylinweb
页面创建project
第二步:将hive
中事实表数据导入kylin
;
第三步:选择要加载的哪些实例库中的哪些表,这里加载kylin_sale
表;
选择该表数据点击sync
同步。
查看页面:
model
如上图:点击 new model
,出现以下视图:
给model
起个名字,然后点击next
;
如上图所示:选择事实表。然后进入下一步,进入到选择维度字段页面,如下图所示:
在这里,我们选择day
,market
,category
,item
四个维度,即我们将从这四个维度来统计分析商品的销售额情况.
点击next
进入到measure(度量)页面,在这里我们选择number
和sales
字段,即我们将统计商品数量和商品销售额两个指标。
最后进入到设置页面,我们可以选择分区字段,选择格式化时间方式, 下面的filter
可以添加where
条件对数据源中的数据做过滤,最后点击save
保存,既model
创建成功。
cube
如上图所示:指明要创建的cube
在哪个model
下,并且对cube
进行命名,然后点击下一步。
如上图所示,对本次cube
添加计算的维度,进入到如下页面,我们选择select all
所有4个维度。
指定要进行计算的度量指标(Measures
)如下图:
在这里,我们先创建一个销售额度指标,即对销售字段进行sum
累加,如上图所示 1,2,3,4步骤。同时我们再创建一个商品数量指标如下图所示:
最终我们会看见两个度量指标(measures
)如下图:
接着一直点击下一步,最终保存cube
。
新创建的 Cube 只有定义,而没有计算的数据,它的状态是 DISABLED ,是不会被查询引擎挑中的。要 想让 Cube 有数据,还需要对它进行构建 build 。
Cube 的构建方式通常有两种:全量构建和增量构建; 两者的构建步骤是完全一样的,区别只在于构建时读取的数据源是全集还是子集。
点击 build ,对定义好的 cube 进行构建计算。构建计算完 cube 后,就可以在 Insight 页面进行查询 操作。
Kylin的Cube创建过程详解
之前我们成功创建了 kylin 的 cube ,并且可以使用 web ui 查询.但除了通过 web ui 进行操作,我们还 可以使用 api 调用。
在使用之前,我们要先进行认证,目前 Kylin 使用 basic Authentication 。 Basic Authentication 是一种非常简单的访问控制机制,它先对账号密码基于 Base64 编码,然后将其作为请 求头添加到 HTTP 请求头中,后端会读取请求头中的账号密码信息以进行认证。 以 Kylin 默认的账号密码 ADMIN / KYLIN 为例,对相应的账号密码进行编码后,结果为 Basic QURNSU46S1lMSU4= ,那么 HTTP 对应的头信息 则为 Authorization:Basic QURNSU46S1lMSU4= 。
curl -X POST -H "Authorization: Basic QURNSU46S1lMSU4=" -H 'Content-Type:
application/json' http://localhost:7070/kylin/api/query -d '{
"sql":"select market,sum(sales) from kylin_sale group by market",
"offset":0,
"limit":10,
"acceptPartial":false,
"project":"my_kylin"
}'
参数解释:
package com.xxxx.kylin;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.apache.kylin.jdbc.Driver;
public class KylinJdbc {
public void connentJdbc() throws InstantiationException, IllegalAccessException,ClassNotFoundException, SQLException {
Driver driver = (Driver)
Class.forName("org.apache.kylin.jdbc.Driver").newInstance();
Properties info = new Properties();
info.put("user", "ADMIN");
info.put("password", "KYLIN");
Connection conn =
driver.connect("jdbc:kylin://*.*.*.*:7070/learn_kylin", info);
Statement state = conn.createStatement();
ResultSet resultSet = state.executeQuery("select * from kylin_account
limit 1000");
while (resultSet.next()) {
String i = resultSet.getString(3);
System.out.println(i);
}
}
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {
KylinJdbc ky = new KylinJdbc();
ky.connentJdbc();
}
}
Cube
划分为多个Segment
,每个Segment
用起始时间和结束时间来标志。Segment
代表一段时间内源数据的预计算结果。Segment
的起始时间等于它之前那个Segment
的结束时间,同理,它的结束时间等于它后面那个Segment
的起始时间。Cube
下不同的Segment
除了背后的源数据不同之外,其他如结构定义、构建过程、优化方法、存储方式等都完全相同。 在全量构建中,只存在唯一的一个Segment
,该Segment
没有分割时间的概念,因此也就没有起始时间和结束时间。全量构建和增量构建各有其适用的场景,用户可以根据自己的业务场景灵活地进行切换。全量构建和增量构建的详细对比如下图:
对于全量构建来说,每当需要更新Cube
数据的时候,它不会区分历史数据和新加入的数据,也就是说,在构建的时候会导入并处理所有的原始数据。
而增量构建只会导入新Segment
指定的时间区间内的原始数据,并只对这部分原始数据进行预计算。
Cube
用于增量构建,Cube
的定义必须包含一个时间维度,用来分割不同的Segment
,我们将这样的维度称为分割时间列 (Partition Date Column
)。分割时间列既可以是Hive
中的**Date
类型**、也可以是**Timestamp
类型或 String
类型。无论是哪种类型,Kylin
都要求用户显式地指定分割时间列的数据格式**,例如精确到年月日的Date
类型(或者String
类型)的数据格式可能是yyyyMMdd
或yyyy-MM-dd
,如果是精确到时分秒的Timestamp
类型(或者String
类型),那么数据格式可能是YYYY-MM-DD HH:MM:SS
。
Web UI
上触发Cube
的增量构建与触发全量构建的方式基本相同。在Web UI
的Model
页面中,选中想要增量构建的Cube
,单击 Action
→Build
。不同于全量构建,增量构建的Cube
会在此时弹出对话框让用户选择End Date
。 Kylin
要求增量Segment
的起始时间等于 Cube
中最后一个Segment
的结束时间,因此当我们为一个已经有Segment
的Cube
触发增量构建的时候,Start Date
的值已经被确定,且不能修改。
仅当Cube
中不存在任何Segment
,或者不存在任何未完成的构建任务 时,Kylin
才接受该Cube
上新的构建任务。
而如果不是web GUI
触发,你通过restAPI
触发的话,那么要确保你给定的时间,不重合。
Cube Designer
的**Refresh Settings
的页面中有Auto Merge Thresholds
和Retention Threshold
两个设置项可以用来帮助管理Segment
**碎片。 Auto Merge Thresholds
允许用户设置几个层级的时间阈值,层级越靠后,时间阈值就越大。举例来说,用户可以为一个Cube
指定(7天、28天)这样的层级。每当Cube
中有新的Segment
状态变为READY
的时候,就会触发一次系统试图自动合并的尝试。系统首先会尝试最大一级的时间阈值,结合上面的(7天、28天)层级的例子,首先查看是否能将连续的若干个Segment
合并成为一个超过28天的大Segment
,在挑选连续Segment
的过程中,如果遇到已经有个别Segment
的时间长度本身已经超过了28天,那么系统会跳过该Segment
,从它之后的所有Segment
中挑选连续的累积超过28天的Segment
。如果满足条件的连续Segment
还不能够累积超过28天,那么系统会使用下一个层级的时间阈值重复寻找的过程。每当找到了能够满足条件的连续Segment
,系统就会触发一次自动合并Segment
的构建任务,在构建任务完成之后,新的Segment
被设置为READY
状态,自动合并的整套尝试又需要重新再来一遍。
举例来说,如果现在有A~H
8个连续的Segment
,它们的时间长度分别 为28天(A)、7天(B)、1天(C)、1天(D)、1天(E)、1天(F)、1天(G)、1天 (H)。此时第9个Segment
I加入,它的时间长度为1天,那么现在Cube
中总 共存在9个Segment
。系统首先尝试能否将连续的Segment
合并到28天这个阈值上,由于Segment
A已经超过28天,它会被排除。
接下来的B到H加起 来也不足28天,因此第一级的时间阈值无法满足,退一步系统尝试第二级的时间阈值,也就是7天。系统重新扫描所有的Segment
,发现A和B已经超过7天,因此跳过它们,接下来发现将Segment
C到I合并起来可以达到7 天的阈值,因此系统会提交一个合并Segment
的构建请求,将Segment
C到I 合并为一个新的Segment
X。X的构建完成之后,Cube
中只剩下三个 Segment
,分别是原来的A(28天),B(7天)和新的X(7天)。由于X的加入,触发了系统重新开始整个合并尝试,但是发现已经没有满足自动合并的条件,既没有连续的、满足条件的、累积超过28天的Segment,也没有连续的、满足条件的、累积超过7天的Segment
,尝试终止。
Segment
从碎片管理的角度来说,自动合并是将多个Segment
合并为一个 Segment
,以达到清理碎片的目的。保留Segment
则是从另外一个角度帮助实现碎片管理,那就是及时清理不再使用的Segment
。
在很多业务场景 中,只会对过去一段时间内的数据进行查询,例如对于某个只显示过去1 年数据的报表,支撑它的Cube
事实上只需要保留过去一年内的Segment
即可。由于数据在Hive
中往往已经存在备份,因此无需再在Kylin
中备份超过一年的历史数据。
在这种情况下,我们可以将**Retention Threshold
设置为365**。每当有新的Segment
状态变为READY
的时候,系统会检查每一个Segment
:如果它的结束时间距离最晚的一个Segment
的结束时间已经大于Retention Threshold
,那么这个Segment
将被视为无需保留。系统会自动地从Cube
中 删除这个Segment
。
如果启用了Auto Merge Thresholds
,那么在使用Retention Threshold
的时候需要注意,不能将Auto Merge Thresholds
的最大层级设置得太高。假设我们将Auto Merge Thresholds
的最大一级设置为1000天,而将Retention Threshold
设置为365天,那么受到自动合并的影响, 新加入的Segment会不断地被自动合并到一个越来越大的Segment
之中,但是这样会导致kylin
要去不断地更新这个大Segment
的结束时间,从而导致这个大 Segment
永远不会得到释放。因此,推荐自动合并的最大一级的时间不要超过1年。
在实际应用场景中,我们常常会遇到这样的问题:由于ETL
过程的延迟,业务每天都需要刷新过去N天的Cube
数据。
举例来说,客户有一个报表每天都需要更新,但是每天的源数据更新不仅包含了当天的新数据, 还包括了过去7天内数据的补充。
一种比较简单的方法是,每天在Cube
中增量构建一个长度为一天的Segment
,这样过去7天的数据就会以7个 Segment
的形式存在于Cube
之中。Cube
的管理员除了每天要创建一个新的 Segment
代表当天的新数据(BUILD
操作)以外,还需要对代表过去7天的7个Segment
进行刷新(REFRESH
操作,Web UI
上的操作及Rest API
参数与 BUILD
类似,这里不再详细展开)。这样的方法固然可以奏效,但是每天为每个Cube
触发的构建数量太多,容易造成Kylin
的任务队列堆积大量未能完成的任务。
上述简单方案的另外一个弊端是,每天一个Segment
也会让Cube
中迅速地累积大量的Segment
,需要Cube管理员手动地对历史已经超过7天的 Segment
进行合并,期间还必须小心地操作,不能错将7天内的Segment
一 起合并了。
举例来说,假设现在有100个Segment
,每个Segment
代表过去的一天的数据,Segment
按照起始时间排序。在合并时,我们只能挑选前面 93个Segment
进行合并,如果不小心把第94个Segment
也一起合并了,那么 当我们试图刷新过去7天(94100)的`Segment`的时候,会发现为了刷新第94天的数据,不得不将193的数据一并重新计算,这对于刷新来说是一种极大的浪费。而且最差的情况就是即使使用之前所介绍的自动合并的功能,类似的问题也仍然存在。
解决思路:
Segment
, 而是以N天为单位创建新的Segment
。 举例来说,假设用户每天更新Cube
的时候,前面7天的数据都需要更新一下,也就是说,如果今天是01-08, 那么用户不仅要添加01-08的新数据,还要同时更新01-01到01-07的数据。在这种情况下,可设置N=7作为最小Segment
的长度。在第一天01-01, 创建一个新的Segment
A,它的时间是从01-01到01-08,我们知道Segment
是起始时间闭,结束时间开,因此Segment
A的真实长度为7天,也就是01-01到01-07。即使在01-01当天,还没有后面几天的数据,Segment
A也能正 常地构建,只不过构建出来的Segment
其实只有01-01一天的数据而已。从 01-02到01-07的每一天,我们都要刷新Segment
A,以保证1日到7日的数据保持更新。由于01-01已经是最早的日期,所以不需要对更早的数据进行更新。
到01-08的时候,创建一个新的Segment
B,它的时间是从01-08到01-15。此时我们不仅需要构建Segment
B,还需要去刷新Segment
A。因为01-01到01-07中的数据在01-08当天仍然可能处于更新状态。在接下来的01-09到01-14,每天刷新A、B两个Segment
。等到了01-15这天的时候,首先创建一个新的Segment
C,它的时间是从01-15到01-22。在01-15当天, Segment
A的数据应当已经被视作最终状态,因为Segment
A中的最后一天 (01-07)已经不再过去N天的范围之内了。因此此时接下来只需要照顾 Segment
B和Segment
C即可。
由此可以看到,在任意一天内,我们只需要同时照顾两个Segment
, 第一个Segment
主要以刷新近期数据为主,第二个Segment
则兼顾了加入新数据与刷新近期数据。这个过程中可能存在少量的多余计算,但是每天多余计算的数据量不会超过N天的数据量。这对于Kylin
整体的计算量来说是可以接受的。根据业务场景的不同,N可能是7天,也有可能是30天,我们可以适度地把最小的Segment
设置成比N稍微大一点的数字。
例如N为7的时候,我们可以设置为10天,这样即使ETL
有时候没有能够遵守N=7的约定,也仍然能够刷新足够的数据。
cuboid
以及cube
优化再来回顾一下kylin
中这两个名词的描述。
Cuboid = one combination of dimensions 维度聚合的物化视图
Cube = all combination of dimensions (all cuboids)
按照dimension
大小顺序排序,从Base Cuboid
开始,依次基于上一层Cuboid
的结果进行再聚合。每一层的计算都是一个单独的Map Reduce(Spark)
任务。
理论上来说,一个N维的Cube
,便有2的N次方种维度组合,参考kylin
官网上的一个例子,一个Cube
包含time, item, location, supplier
四个维度,那么组合(Cuboid
)便有16种。
一个Cube
中,当维度数量N超过一定数量后,空间以及计算消耗将会非常大,比如说10维那就是1024个cuboid
,但我们真正查询的时候可能只会用到其中的100个cuboid,如果不做优化那么会出现以下几个问题
会使得Build
出来的Cube Size
很大,从而占用大量的磁盘空间;
Cube Building
的时间会很长 ;
会占用集群的计算资源 。
Cube
剪枝优化Aggravation Group
Kylin
在定义Cube
时候,可以将维度拆分成多个聚合组(Aggregation Groups
)这也就是我们在web
页面创建cube
时的第5步可以做,只在组内计算Cube
,聚合组内查询效率高,跨组查询效率较差,所以需要根据业务场景,将常用的维度组合定义到一个聚合组中,提高查询性能,这也是Kylin
中查询性能优化的一个重要方面。
举例:
业务场景有4个维度,分别为ABCD,如果聚合组含有的维度为ABCD的话,它的Cuboid为24=16个。但是此时如果AB是一个聚合组,CD是一个聚合组,那么Cuboid的个数就是22+22=8个,相当于缩减了一半。即原来2(K+M+N)个Cuboid可以减少到2K+2M+2N个。
查看cube所生成的cuboid个数命令如下:
kylin.sh org.apache.kylin.engine.mr.common.CubeStatsReader CubeName
Mandatory Dimensions
) 如果一个维度被定义为强制维度,那么这个分组产生的所有Cuboid
中每一个Cuboid
都会包含该维度。如果根据这个分组的业务逻辑,相关的查询一定会在过滤条件或分组条件中,则可以在该分组中把该维度设置为强制维度。
Hierarchy Dimensions
) 何为层次维度,具有上下级层次关系的维度,例如时间维度 年—>月—>日
,和地区维度 国家(country)—>省份(province)—> 城市(city)
。将有这样关系的维度设置为层次关系,并且将它们设置为层次维度的话,cuboid
数量将下降。
举例:
如果有三个维度A,B,C 设置为层次维度,那么Cuboid数量将由2^3减为3+1(ABC、AB、A、空)。
对应的group by
就变成了如下三种情况
group by country, province, city
(等同于 group by country, city
或者group by city
)
group by country, province
(等同于group by province
)
group by country
。
Hive
里面的数据以时间作为分区,每天处理增量数据,那么我们kylin
也要每天增量写入数据。
每个联合中包含两个或更多个维度,如果某些列形成一个联合,那么在该分组产生的任何Cuboid
中,这些联合维度要么一起出现,要么都不出现。如果根据这个分组的业务逻辑,多个维度在查询中总是同时出现,则可以在该分组中把这些维度设置为联合维度。
例子:如有 A B C D 四个维度, B C维度作为联合维度,则最后只要生成7个维度,如下图所示:其中红色维度为无需生成的维度,黑色维度为需要生成的维度
时间维度表,里面充斥着用途各异的时间维度,例如每个日期所处的季度、月份等。这些维度可以被地用来进行各个时间粒度上的聚合分析,而不需要进行额外的上卷(Roll Up
)操作。但是为了这个目的一下子引入这么多个维度,导致 Cube
中总共的Cuboid
数量呈现爆炸式的增长往往是得不偿失的。
当用户需要以更高的粒度(比如按周、 按月)来聚合时,如果在查询时获取按日聚合的Cuboid
数据,并在查询引擎中实时地进行上卷操作,那么就达到了使用牺牲一部分运行时性能来节省Cube
空间占用的目的。
Kylin
将这样的理念包装成为了一个简单的优化工具——衍生维度。
衍生维度用于在有效维度内将维度表上的非主键维度排除掉,并使用维度表的主键(其实是事实表上相应的外键)来替代它们。Kylin
会在底层记录维度表主键与维度表其他维度之间的映射关系,以便在查询时能够动态地将维度表的主键翻译成这些非主键维度,并进行实时聚合。
例如,假设有一个日期查找表,其中的cal_dt
是主键列,还有许多派生列,例如week_begin_dt
,mont9h_begin_dt
。即使分析人员需要week_begin_dt
作为维度,我们也可以对其进行修剪,因为它始终可以从cal_dt
维度进行计算,这是“派生”优化。
只会计算事实表中的正常维度的组合数据,因为事实表中的粒度暂时认为是最细粒度的结果,可以通过上卷切出更粗粒度的结果。(减少存储,牺牲掉部分简单计算的查询性能)
编码(Encoding
)代表了该维度的值应使用何种方式进行编码,合适的编码能够减少维度对空间的占用,例如,我们可以把所有的日期都用三个字节进行编码,相比于字符串存储,或者是使用长整数形式存储的方法,我们的编码方式能够大大减少每行Cube
数据的体积。而Cube
中可能存在数以亿计的行数,使用编码节约的空间累加起来将是一个非常巨大的数字。
目前Kylin
支持的编码方式有以下几种:
Date
编码:将日期类型的数据使用三个字节进行编码,其支持从 000-01-01
到9999-01-01
中的每一个日期。Time
编码:仅支持表示从**1970-01-01 00:00:00
到2038-01-19 03:14:07
的时间**,且Time-stamp
类型的维度经过编码和反编码之后,会失去毫秒信息,所以说Time
编码仅仅支持到秒。但是Time
编码的优势是每个维度仅仅使用4个字节,这相比普通的长整数编码节约了一半。如果能够接受秒级的时间精度,请选择Time
编码来代表时间的维度。Integer
编码:Integer
编码需要提供一个额外的参数Length
来代表需要多少个字节。Length
的长度为1~8。如果用来编码int32
类型的整数,可以将Length
设为4;如果用来编码int64
类型的整数,可以将Length
设为8。在更多情况下,如果知道一个整数类型维度的可能值都很小,那么就能使用 Length
为2甚至是1的int
编码来存储,这将能够有效避免存储空间的浪费。Dict
编码:对于使用该种编码的维度,每个Segment
在构建的时候都会为这个维度所有可能的值创建一个字典,然后使用字典中每个值的编号来编码。Dict
的优势是产生的编码非常紧凑,尤其在维度值的基数较小且长度较大的情况下,特别节约空间。由于产生的字典是在查询时加载入构建引擎和查询引擎的,所以在维度的基数大、长度也大的情况下,容易造成构建引擎或查询引擎的内存溢出。Fixed_length
编码:编码需要提供一个额外的参数Length
来代表需 要多少个字节。该编码可以看作**Dict
编码的一种补充**。对于基数大、长度也大的维度来说,使用Dict
可能不能正常工作,于是可以采用一段固定长度的字节来存储代表维度值的字节数组,该数组为字符串形式的维度值的UTF-8
字节。如果维度值的长度大于预设的Length
,那么超出的部分将会被截断。Fixed_Length_Hex
编码:适用于字段值为十六进制字符,比如1A2BFF
或者FF00FF
,每两个字符需要一个字节。只适用于varchar
或nvarchar
类型。