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:

        12    女

    如果要按照求出男的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,设置过期时间和合并时间

 

posted on 2019-11-25 19:15  李昊宗  阅读(651)  评论(0编辑  收藏  举报