0、维和度量
事实表Fact Table:
事实表里面主要包含两方面的信息:维和度量
事实表中的维:关联到维表的键,并不记录具体信息;
事实表中度量:一般都会记录事件的相应数值,比如产品的实付金额等
维度表Lookup Table:
Lookup Table包含对事实表的某些列进行扩充说明的字段
2、olap
下钻(Drill-down):在维的不同层次间的变化,从上层降到下一层,比如通过对2016年第二季度的总销售数据进行钻取来查看2016年第二季度4、5、6每个月的消费数据
上卷(Roll-up): 钻取的逆操作,即从细粒度数据向更高汇总层的聚合,如将江苏省、上海市和浙江省的销售数据进行汇总来查看江浙沪地区的销售数据
切片(Slice): 选择维中特定的值进行分析,比如只选择电子产品的销售数据
切块(Dice): 选择维中特定区间的数据或者某批特定值进行分析,比如选择2016年第一季度到2016年第二季度的销售数据
旋转(Pivot): 即维的位置的互换,就像是二维表的行列转换,如图2-3中通过旋转实现产品维和地域维的互换。
3、Apache Kylin
Apache Kylin是一个开源的分布式分析引擎!中文名麒麟,是首个完全由中国团队设计开发的Apache顶级项目,目的是为多维分析(OLAP)提供亚秒级的响应速度
起源思路:
维度也不会大到天文数字这样的级别,展示需求也是如此,那么有价值的“维度组合个数”也是相对有限的,一般不会随着数据量的增加,维度也跟着不断的增加。
Kylin核心(预计算):
理论基础:空间换时间,闲时定期对已有数据做预计算,并保存成Cube并存在HBase中。
基础概念:
Cuboid:Kylin中将维度任意组合成为一个Cuboid。
Cube: Kylin中将所有维度组合成为一个Cube,即包含所有的Cuboid
比如有三个字段:
telephone(电话号码)、date(通话建立的时间)、duration(单次通话时间),
0维度和3维度的共有2个,1维度3个,2维度3个。共2^3 = 8个维度组合。
每个维度组合的聚合(sum,count等)结果就保存在这每个Cuboid上
注:Kylin在查询时,根据SQL找到对应的Cuboid,不再去扫描原始数据集,。
缺点:
存储Cube所消耗的空间一般是原始数据集大小的20~100倍,即原始数据100GB,那么构建出的物化视图大概为20 * 100GB = 2TB。
4、比较
Apache Drill
Apache Impala
Druid
Hive
Presto(Facebook)
SparkSQL
5、部署
①单机部署
job的管理(当点击build产生的job)、sql的查询集于一身 //当构建cube时候,会预计算cuboid,默认是MR任务,所以会产生很多的job
②分布式部署
job的管理(当点击build产生的job)、sql的查询进行分离 //job节点一个,查询节点多个
③分布式高可用部署
job的管理(当点击build产生的job)、sql的查询进行分离 //对job节点做了高可用,允许多个
④分布式读写分离部署
Kylin 部署在两个集群上,如下:
一个 Hadoop 集群用作 Cube 构建
一个 HBase 集群用作 SQL 查询
注:对于分布式,一般都会结合Nginx,对查询的连接做负载均衡
6、权限控制
query:适用于只需在项目中有查询表/cube 权限的分析师。
operation:该角色适用于需维护 Cube 的公司/组织中的运营团队。OPERATION 包含 QUERY 的所有权限。
management:该角色适用于充分了解数据/模型商业含义的模型师,建模师会负责模型和 Cube 的设计。MANAGEMENT 包含 OPERATION 和 QUERY 的所有权限。
admin:该角色全权管理项目。ADMIN 包含 MANAGEMENT,OPERATION 和 QUERY 的所有权限。
7、ROWKEY
每次点击build都会在hbase产生一张表,即一个Segment
表中的Rowkey,是由各个维度的值拼接而成的 //用户可以通过拖曳的方式调整各个维度在Rowkeys上的顺序
Rowkey中的维度值并不是我们看见的维度值,Kylin会对它们进行编码和压缩;默认是字典(Dictionary)编码; //除了字典以外,还有整数(Int)和固定长度(Fixed Length)的编码。
字典编码:
字典编码是将此维度下的所有值构建成一个从string到int的映射表;
Kylin会将字典序列化保存在Cube中存储int值,从而大大减小存储的大小!!!!!
比如有性别、是否带眼镜两个维度列,那么RowKey :男带、男不带、女带、女不带 ?
根据字典编码:
男:1,女:2 是一个字典
带:1,不带:2 是一个字典
所以Rowkey就是各个维度编码后的int值的组合,并不是字符串!!!
注意:
①用户可以通过拖曳的方式调整各个维度在Rowkeys上的顺序 //由于rowkey是有序的,所以一定要将过滤维度放在前面
②用户可以自己定义列族,将预计算的值(count、sum等)分配给同一RowKey不同的列族
8、全量与增量构建cube
全量构建:
事实表的数据不是按时间增长的、表数据更新的频率低
增量构建:
基本概念:
对于增量构建一个Segment的Cube,需要用户输入起始时间、结束时间
kylin去拉取HIVE数据进行构建,则自动会带上此时间条件:
where KYLIN_PART_DT >= '2019-09-03' AND KYLIN_PART_DT < '2019-09-04' //注意左闭右开
历史数据刷新(重新构建历史数据):
kylin已经有这个时间段的segment,但是hive里面这个时间段的数据发生了变化,需要重新计算!!
在刷新的同时,Cube仍然可以被查询,只不过返回的是陈旧数据。当Segment刷新完毕时,新的Segment会立即生效,查询开始返回最新的数据。老Segment则成为垃圾,等待回收。
合并:
由于每次build都会产生表(Segment),所以需要合并。 //所以hive分区字段dt,一定也要作为一个维度!!!否则合并之后不能看见某分区的预聚合
①自己设置间隔天数
②默认有新的segment Ready就触发自动合并
流式构建:
必要条件:
①消息的格式必须是json
② json中至少包含一个时间字段,并且是长整型!
原理:
流式构建需要达到分钟级的数据更新频率,Kylin的做法是每隔数分钟就启动一次微构建,用于处理最新的一批数据。
这种做法的理念有一些类似于Spark Streaming
①Kylin流式构建怎么保证数据不丢失?
Kylin流式构建生成的Segment是由消息的起始offset和终止offset,offset是顺序递增的且没有重叠和遗漏,
这将确保没有数据丢失,一个消息只会被消费一次!!!!
②为什么要求投递到Kafka中的JSON数据结构中要包含一个业务时间戳?
业务时间戳的粒度太细,无法在时间维度上完成深度的聚合,Kylin会自动帮我们生成一些衍生时间字段,这些衍生时间字段的值都来源于业务时间戳。
③ Kafka数据延迟问题对Kylin有影响吗?
流式构建的每个Segment除了包含起始offset,还包含数据集的StartDate和EndDate,这两个属性来源于上面说的衍生时间字段。
当用户按时间条件查询时,Kylin将扫描与查询时间范围相匹配的所有段。譬如:
有三个Segment,它们的offset依次连续且无重叠(左包右闭),
Seg[100-400]中的消息时间跨度是1:04 – 1:11,
Seg[400 - 2000]的时间跨度是1:08 – 1:40。
当用户要查询1:10的统计信息时,Kylin发现这两个Segment都可能有这个时间的消息,故而会扫描这两个Segment然后再次做汇总计算
9、错误构建的处理
如果存在一个ERROR状态的构建任务,那么用户需要先处理好该构建任务,然后才能成功地向Kylin提交新的构建任务
解决:
首先在Web GUI或后台的日志中查找构建失败的原因,解决问题后回到Monitor页面,选中失败的构建任务,
单击Action→Resume,恢复该构建任务的执行。
Resume操作会跳过之前所有已经成功了的子步骤,直接从第一个失败的子步骤重新开始执行
10、数据漂移问题
用户每日增量的数据,包含了过去的7天的数据。
如果我们按照每日增量去做,今天的数据计算完,并且要刷新前七天的segment,如果每30天合并一次segment,那么前七天的数据被合并了,系统会将前30天的segment都重新计算!!!
解决办法:
我们创建的一个segment包含7天的数据,比如1号~7号,如果当前时间在这个时间段,刷新这个segment, 并且刷新之前的segment(22号 ~ 30号)
并且还要合理的设置合并规则,让最近14天的数据都不会被合并
11、查询
Cube构建好以后,状态变为“READY”,就可以进行查询了 注意:Group By的列和Where条件里的列,必须是在Dimension中定义的列,而SQL中的度量,应该跟Cube中定义的度量相一致!!!! 也就是说数据查询是从hbase中而来,如果hbase没有这个数据,那么就会查询报错!! 如果要查询name,那么name也必须作为一个维度!!!!!! select name from person group by name SQL语法(作为olap工具,只支持select,不支持update等): ROW_NUMBER、RANK、DENSE_RANK FIRST_VALUE、LAST_VALUE LAG、LEAD 支持了hive中的窗口函数,其他的语法都差不多 一张事实表a,有性别这个维度,值是1,2 还有一张性别的维度表b: 1 男 2 女 如果要按照求出男的count: SELECT count(*),b.sex_name FROM a INNER JOIN b AS b ON a.sex_code = b.sex_code group by a.sex_code //join后,按照sex_code分组,查出sex_name 这是错误的,按照sex_code分组,只能查出sex_code: SELECT count(*),a.sex_name FROM a INNER JOIN b AS b ON a.sex_code = b.sex_code group by b.sex_name
12、cube优化
①维度优化(Cubeid减少) 膨胀率:当前Cube的大小除以源数据大小的比例,称为膨胀率(Expansion Rate) 在Web GUI的Model页面选择一个READY状态的Cube,当我们把光标 移到该Cube的Cube Size列时,Web GUI会提示Cube的源数据大小,以及膨胀率 比如,有12个维度,Kylin就会计算2的12次方即4096个cuboid,实际上查询可能用到的cuboid不到1000个,甚至更少。 如果对维度不进行优化,会造成集群计算和存储资源的浪费,也会影响cube的build时间和查询性能,所以我们需要进行cube的维度优化 聚集组 衍生纬度 强制维度 层次维度 联合维度 Extended Column 衍生维度: 维表中可以由主键推导出值的列可以作为衍生维度。 例如用户维表可以从userid推导出用户的姓名,年龄,性别。 优化效果:维度表的N个维度组合成的cuboid个数会从2的N次方降为2。 注:新建cube,在我们添加事实表之后,添加补充的维度表,将其中的维度设置为Derived,不设置为normal 注:一个用户姓名维度表,两个字段,name_id,name_code,事实表中有一个name_id字段 事实表中的name_id为一个维度 维度表中的name_id、name_code也应该是一个维度,只不过是衍生维度,将其设置为normal 这样不管是事实表还是维度表,其中的字段都能进行where过滤 Aggregation Groups聚合组: 用户根据自己关注的维度组合,可以划分出自己关注的组合大类。 如果用户仅仅关注维度 AB 组合和维度 CD 组合,那么该 Cube 则可以被分化成两个聚合组,分别是聚合组 AB 和聚合组 CD,生成的 Cuboid 数目从 16 个缩减成了 8 个。 同时,用户关心的聚合组之间可能包含相同的维度,例如聚合组 ABC 和聚合组 BCD 都包含维度 B 和维度 C。这些聚合组之间会衍生出相同的 Cuboid, Cuboid为这些聚合组所共有。 强制维度:用户查询必须加上的维度 层次维度:维度是有层级的,比如省市县 联合维度:哪些维度是必定会一起用的 必要维度Mandatory Dimensions:做任何查询都必须带的维度 层级维度Hierarchy Dimensions:省 ---> 市 ---->区 联合维度Joint Dimensions:常常放在一起的维度 Extended Column: 在OLAP分析场景中,经常存在对某个id进行过滤,但查询结果要展示为name的情况,比如user_id和user_name。 这类问题通常有三种解决方式: a. 将ID和Name都设置为维度,查询语句类似select name, count(*) from table where id = 1 group by id,name。这种方式的问题是会导致维度增多,导致预计算结果膨胀; b. 将id和name都设置为维度,并且将两者设置为联合。这种方式的好处是保持维度组合数不会增加,但限制了维度的其它优化,比如ID不能再被设置为强制维度或者层次维度; c. 将ID设置为维度,Name设置为特殊的Measure,类型为Extended Column。这种方式既能保证过滤id且查询name的需求,同时也不影响id维度的进一步优化。 也就是说,不将id设为维度,那么永远不能按照id过滤(where id = 1),因为hbase中只保存了按照维度聚合的数据!!! id和name一定会是一一对应的,所以设为联合维度,或者将其设为扩展字段更好!!!! ②Rowkeys优化 维度编码: Date编码:将日期类型的数据使用三个字节进行编码,其支持从0000-01-01到9999-01-01中的每一个日期 ·Integer编码:Integer编码需要提供一个额外的参数“Length”来代表需要多少个字节 Dict编码:在维度值的基数较小且长度较大的情况下,特别节约空间,但由于字典要被加载到Kylin内存中,在超高基情况下,可能引起内存不足的问题。 Fixed_length编码:编码需要提供一个额外的参数“Length”来代表需要多少个字节,编码结果是字符串形式 所以: 时间用Date编码 维度基数小,用Dict编码 维度基数大,选择Integer、Fixed_length编码 注:在很多行业,身份证号码可能就是一个重要的维度,但是身份证号码由于其具有特殊性而不能使用整数类型的编码(身份证最后一位可能是X), 其高基数的特点也决定了不能使用dict编码,在目前的版本中只能使用fixed_length编码,但是显然fixed_length不能充分利用身份证号码中大部分字节是数字的特性 来进行深度编码,因此存在一定程度的浪费。 维度分片: 我们先来看看Rowkey的组成: shard id + cuboid id + dimension values cuboid id: cuboid cuboid_id ABC 7(111) AB 6(110) BC 5(101) AC 4(100) A 3(011) B 2(010) C 1(001) dimension values: 就是我们在界面上调整的rowkey顺序。比如A维度有100个维度值,B维度有100个维度值,C维度有100个维度值 这个Cubeid在hbase就会有:100*100*100=1000000行 每行存储的就是不同维度值的count、sum等 dimension values是通过维度值进行编码之后的(dict、date等) shard id: 可以选择一个维度,那么这个维度值编码会卸载RowKey的最前面! 那么同一个维度的信息必定在同一个HRegin Server 一般推荐将用户经常使用或者基数很大的维度放在前面,这样在查询的时候有利用提高扫描效率。 RowKey顺序: 在界面通过拖拽,调整不同维度的顺序! ③清理 及时清理无用的Segment,设置过期时间和合并时间