Apache Kylin 概覽

擴展閱讀:

一、什么是 Kylin

Apche Kylin 是 Hadoop 大數據平臺上的一個開源 OLAP 引擎。它采用多維立方體(Cube)預計算技術,可以將某些場景下的大數據 SQL 查詢速度提升到亞秒級別。相對于之前的分鐘乃至小時級別的查詢速度。

Apache Kylin 也是中國人主導的,第一個 Apche 頂級開源項目,在開源社區有較大影響力。

Kylin 對于解決的問題有以下假設:

  • 大數據查詢要的一般是統計結果,是多條記錄經過聚合函數計算后的統計值
    • 原始的記錄則不是必需的,或者訪問頻率和概率都極低
  • 聚合是按維度進行的,有意義的維度聚合組合也是相對有限的,一般不會隨著數據的膨脹而膨脹

基于以上兩點,可以得到一個新的思路—預計算,應盡量多地預先計算聚合結果,在查詢時應該盡量利用預計算的結果得出查詢結果,從而避免直接掃描可能無限增大的原始記錄

二、定義 Cube

2.1、什么是 Cube

Cube 即多維立方體,也叫數據立方體。如下圖所示,這是由三個維度(維度數可以超過3個,下圖僅為了方便畫圖表達)構成的一個OLAP立方體,立方體中包含了滿足條件的cell(子立方塊)值,這些cell里面包含了要分析的數據,稱之為度量值。

  • 立方體:由維度構建出來的多維空間,包含了所有要分析的基礎數據,所有的聚合數據操作都在立方體上進行
  • 維度:觀察數據的角度。一般是一組離散的值,比如:
    • 時間維度上的每一個獨立的日期
    • 商品維度上的每一件獨立的商品
  • 度量:即聚合計算的結果,一般是連續的值,比如:
    • 銷售額,銷售均價
    • 銷售商品的總件數
  • 事實表:是指存儲有事實記錄(明細數據)的表,如系統日志、銷售記錄等;事實表的記錄在不斷地動態增長,數據量大
  • 維度表(維表):保存了維度值,可以跟事實表做關聯。常見的維度表如:
    • 日期表
    • 地點表
    • 分類表
  • Cuboid:對于每一種維度的組合,將度量做聚合運算,然后將運算的結果保存為一個物化視圖,稱為 Cuboid

2.2、創建數據模型

2.2.1、數據模型

常見的多維數據模型,如星型模型、雪花模型等。星型模型:有一張事實表、以及零個或多個維度表;事實表與維度表通過 主鍵/外鍵 相關聯,維度表之間沒有關聯,就像很多星星圍繞在一個恒星周圍,顧命名為星型模型。

雪花模型:如果將星型模型中某些維度的表再做規范,抽取成更細的維度表,然后讓維度表之間也進行關聯,那么這種模型成為雪花模型(雪花模型可以通過一定的轉換,變為星型模型)

2.2.2、創建模型

  • Model 是 Cube 的基礎,用于描述一個數據模型
  • 有了數據模型,定義 Cube 可以直接從此模型定義的表和列中進行選擇
  • 基于一個數據模型可以創建多個 Cube

數據模型可用一個 json 表示,如下是一個例子:

{
  "name": "test_model",                 // 名為 test_model 的數據模型
  "fact_table": "DEFAULT.KYLIN_SALES",  // 事實表為 DEFAULT.KYLIN_SALES
  "lookups": [                          // 維表(又叫查找表,即lookup表)為 DEFAULT.KYLIN_CAL_DT;維表可以是 0~n 個
    {
      "table": "DEFAULT.KYLIN_CAL_DT",
      "kind": "LOOKUP",
      "alias": "KYLIN_CAL_DT",
      "join": {
        "type": "inner",                // KYLIN_SALES 與 KYLIN_CAL_DT 連接方式為 inner join
                                        // join condition 為 KYLIN_CAL_DT.CAL_DT = KYLIN_SALES.PART_DT
        "primary_key": [
          "KYLIN_CAL_DT.CAL_DT"
        ],
        "foreign_key": [
          "KYLIN_SALES.PART_DT"
        ]
      }
    }
  ],
  "dimensions": [                       // 定義維度,維度可以來自于事實表和維表;后續基于該模型的 Cube 的維度只能從這里定義的 dimensions 中選
    {
      "table": "KYLIN_SALES",
      "columns": [
        "PART_DT",
        "LEAF_CATEG_ID",
        "LSTG_SITE_ID",
        "SLR_SEGMENT_CD",
        "OPS_USER_ID"
      ]
    },
    {
      "table": "KYLIN_CAL_DT",
      "columns": [
        "AGE_FOR_YEAR_ID",
        "AGE_FOR_QTR_ID",
        "AGE_FOR_MONTH_ID",
        "AGE_FOR_WEEK_ID",
        "CAL_DT"
      ]
    }
  ],
  "metrics": [                        // 定義度量,度量智能來自事實表;后續基于該模型的 Cube 的度量只能從這里定義的 metrics 中選
    "KYLIN_SALES.PRICE",
    "KYLIN_SALES.ITEM_COUNT",
    "KYLIN_SALES.SELLER_ID"
  ],
  "filter_condition": "price > 0",    // 定義向數據源查詢數據時會帶上的過濾條件
  "partition_desc": {                 // 指定 KYLIN_SALES.PART_DT 列作為模型的分割時間列,以支持基于該模型的 Cube 按此列做增量構建
    "partition_date_column": "KYLIN_SALES.PART_DT",
    "partition_time_column": null,
    "partition_date_start": 0,
    "partition_date_format": "yyyy-MM-dd",
    "partition_time_format": "HH:mm:ss",
    "partition_type": "APPEND",
    "partition_condition_builder": "org.apache.kylin.metadata.model.PartitionDesc$DefaultPartitionConditionBuilder"
  },
}

2.3、創建 Cube

高級設置的一些說明:

  • Aggregation Groups:Kylin 默認會把所有維度放在一個聚合組中;如果維度數較多(例如>10),那么建議用戶根據查詢的習慣和模式,將維度分為多個聚合組。通過使用多個聚合組,可以大大降低 Cube 中 Cuboid 數量。如,一個 Cube 有(M+N)個維度,那么會有 2的(M+N)次方 個 Cuboid;如果把這些維度分為兩個不相交的聚合組,那么 Cuboid 的數量將減少為 2的M次方+2的N次方。在單個聚合組中,可以對維度設置高級屬性:
    • Mandatory Dimensions:必要維度。所有不含此維度的 cuboid 就可以被跳過計算
    • Hierarchy Dimensions:層級維度,例如 “國家” -> “省” -> “市” 是一個層級;不符合此層級關系的 cuboid 可以被跳過計算
    • Joint Dimensions:聯合維度,有些維度往往一起出現,或者它們的基數非常接近(有1:1映射關系)。例如 “user_id” 和 “email”。把多個維度定義為組合關系后,所有不符合此關系的 cuboids 會被跳過計算
  • Rowkeys:HBase rowkey上的維度的位置對性能至關重要,可以拖拽維度列去調整其在 rowkey 中位置,位于rowkey前面的列,將可以用來大幅縮小查詢的范圍。通常建議:
    • 將必要維度放在開頭
    • 然后是在過濾 ( where 條件)中起到很大作用的維度
    • 如果多個列都會被用于過濾,將高基數的維度(如 user_id)放在低基數的維度(如 age)的前面,這也是基于過濾作用的考慮
  • Cube Engine:Spark 或 Hive

2.3.1、一個 Cube 例子

Cube 可用一個 json 表示,如下是一個例子:

{
  "name": "test_cube",
  "model_name": "test_model",     // 使用名為 model_test 的數據模型
  "description": "",
  "null_string": null,
  "dimensions": [                 // 維度,可以來自事實表或維度表
    {
      "name": "PART_DT",
      "table": "KYLIN_SALES",
      "column": "PART_DT",
      "derived": null
    },
    {
      "name": "LEAF_CATEG_ID",
      "table": "KYLIN_SALES",
      "column": "LEAF_CATEG_ID",
      "derived": null
    },
    {
      "name": "LSTG_SITE_ID",
      "table": "KYLIN_SALES",
      "column": "LSTG_SITE_ID",
      "derived": null
    },
    {
      "name": "SLR_SEGMENT_CD",
      "table": "KYLIN_SALES",
      "column": "SLR_SEGMENT_CD",
      "derived": null
    },
    {
      "name": "OPS_USER_ID",
      "table": "KYLIN_SALES",
      "column": "OPS_USER_ID",
      "derived": null
    },
    {
      "name": "CAL_DT",
      "table": "KYLIN_CAL_DT",
      "column": "CAL_DT",
      "derived": null
    },
    {
      "name": "AGE_FOR_YEAR_ID",
      "table": "KYLIN_CAL_DT",
      "column": null,
      "derived": [
        "AGE_FOR_YEAR_ID"
      ]
    },
    {
      "name": "AGE_FOR_QTR_ID",
      "table": "KYLIN_CAL_DT",
      "column": null,
      "derived": [
        "AGE_FOR_QTR_ID"
      ]
    },
    {
      "name": "AGE_FOR_MONTH_ID",
      "table": "KYLIN_CAL_DT",
      "column": null,
      "derived": [
        "AGE_FOR_MONTH_ID"
      ]
    },
    {
      "name": "AGE_FOR_WEEK_ID",
      "table": "KYLIN_CAL_DT",
      "column": null,
      "derived": [
        "AGE_FOR_WEEK_ID"
      ]
    }
  ],
  "measures": [                 // 度量,即哪個列做什么聚合計算
    {
      "name": "_COUNT_",
      "function": {
        "expression": "COUNT",
        "parameter": {
          "type": "constant",
          "value": "1"
        },
        "returntype": "bigint"
      }
    },
    {
      "name": "_SUM_",
      "function": {
        "expression": "SUM",
        "parameter": {
          "type": "column",
          "value": "KYLIN_SALES.ITEM_COUNT"
        },
        "returntype": "bigint"
      }
    },
    {
      "name": "_MAX_",
      "function": {
        "expression": "MAX",
        "parameter": {
          "type": "column",
          "value": "KYLIN_SALES.PRICE"
        },
        "returntype": "decimal(19,4)"
      }
    }
  ],
  "dictionaries": [],
  "rowkey": {                 // rowkey 配置,主要關注維度列在 rowkey 中的位置(誰先誰后)
    "rowkey_columns": [
      {
        "column": "KYLIN_SALES.PART_DT",
        "encoding": "date",
        "encoding_version": 1,
        "isShardBy": false
      },
      {
        "column": "KYLIN_SALES.LEAF_CATEG_ID",
        "encoding": "dict",
        "encoding_version": 1,
        "isShardBy": false
      },
      {
        "column": "KYLIN_SALES.LSTG_SITE_ID",
        "encoding": "dict",
        "encoding_version": 1,
        "isShardBy": false
      },
      {
        "column": "KYLIN_SALES.SLR_SEGMENT_CD",
        "encoding": "dict",
        "encoding_version": 1,
        "isShardBy": false
      },
      {
        "column": "KYLIN_SALES.OPS_USER_ID",
        "encoding": "dict",
        "encoding_version": 1,
        "isShardBy": false
      },
      {
        "column": "KYLIN_CAL_DT.CAL_DT",
        "encoding": "date",
        "encoding_version": 1,
        "isShardBy": false
      }
    ]
  },
  "hbase_mapping": {
    "column_family": [
      {
        "name": "F1",
        "columns": [
          {
            "qualifier": "M",
            "measure_refs": [
              "_COUNT_",
              "_SUM_",
              "_MAX_"
            ]
          }
        ]
      }
    ]
  },
  "aggregation_groups": [     // aggregation groups 配置,共兩個 aggregation groups
    {
      "includes": [
        "KYLIN_SALES.PART_DT",
        "KYLIN_SALES.LEAF_CATEG_ID",
        "KYLIN_SALES.LSTG_SITE_ID",
        "KYLIN_SALES.SLR_SEGMENT_CD",
        "KYLIN_SALES.OPS_USER_ID",
        "KYLIN_CAL_DT.CAL_DT"
      ],
      "select_rule": {
        "hierarchy_dims": [],
        "mandatory_dims": [],
        "joint_dims": []
      }
    }
  ],
  "partition_date_start": 0,            // Cube 日期/時間 分區起始值
  "partition_date_end": 3153600000000,  // Cube 日期/時間 分區結束值
  "auto_merge_time_ranges": [           // 自動合并小的 segments 到中等甚至更大的 segment
    604800000,
    2419200000
  ],
  "retention_range": 0,                 // 不刪除舊的 Cube Segment
  "engine_type": 4,                     // 構建 Cube 的引擎為 Spark
  "storage_type": 2,                    // 使用 Hbase 存儲 Cube
  "override_kylin_properties": {},
  "cuboid_black_list": []
}

三、構建 Cube

以使用 Spark 構建 Cube 為例

新創建的 Cube 只有定義,而沒有計算的數據,它的狀態是 “DISABLED” 的,要想讓 Cube 有數據,還需要對它進行構建,Cube 的構建方式通常有兩種:

  • 全量構建:構建時讀取的數據源是全集
  • 增量構建:構建時讀取的數據源是子集

3.1、構建流程

以全量構建為例,Cube 的構建主要包含以下步驟,由構建引擎來調度執行:


Step1: 創建 Hive 大平表

將創建 Cube 涉及到的維度從原有的事實表和維度表中查詢出來組成一條完整的數據插入到一個新的 hive 表中

我們對 2.3.1 小節中舉例的 Cube 進行構建,構建在 Kylin 頁面上進行,構建是需要選擇一個起始時間范圍,我們選擇開始日期為 2012-01-01,結束日期為 2012-08-01,那么構建時就會執行以下命令:

hive -e "USE default;

DROP TABLE IF EXISTS kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3;
CREATE EXTERNAL TABLE IF NOT EXISTS kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3
(
    KYLIN_SALES_PART_DT date
    ,KYLIN_SALES_LEAF_CATEG_ID bigint
    ,KYLIN_SALES_LSTG_SITE_ID int
    ,KYLIN_SALES_SLR_SEGMENT_CD smallint
    ,KYLIN_SALES_OPS_USER_ID string
    ,KYLIN_CAL_DT_CAL_DT date
    ,KYLIN_SALES_ITEM_COUNT bigint
    ,KYLIN_SALES_PRICE decimal(19,4)
)
STORED AS SEQUENCEFILE
LOCATION 'hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393
-d8ca-9dfd1b6a9bb9/kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3';

ALTER TABLE kylin_intermediate_test_cube_44e5fcfe_e62f_375c
_1e91_1d75d2fc6de3 SET TBLPROPERTIES('auto.purge'='true');

-- 根據數據模型定義的 join type 和 join condition 查詢各個維度列并 insert 到 hive 表中
INSERT OVERWRITE TABLE kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3
SELECT
    KYLIN_SALES.PART_DT as KYLIN_SALES_PART_DT
    ,KYLIN_SALES.LEAF_CATEG_ID as KYLIN_SALES_LEAF_CATEG_ID
    ,KYLIN_SALES.LSTG_SITE_ID as KYLIN_SALES_LSTG_SITE_ID
    ,KYLIN_SALES.SLR_SEGMENT_CD as KYLIN_SALES_SLR_SEGMENT_CD
    ,KYLIN_SALES.OPS_USER_ID as KYLIN_SALES_OPS_USER_ID
    ,KYLIN_CAL_DT.CAL_DT as KYLIN_CAL_DT_CAL_DT
    ,KYLIN_SALES.ITEM_COUNT as KYLIN_SALES_ITEM_COUNT
    ,KYLIN_SALES.PRICE as KYLIN_SALES_PRICE
    FROM DEFAULT.KYLIN_SALES as KYLIN_SALES
INNER JOIN DEFAULT.KYLIN_CAL_DT as KYLIN_CAL_DT
    ON KYLIN_SALES.PART_DT = KYLIN_CAL_DT.CAL_DT
WHERE (price > 0)  AND (KYLIN_SALES.PART_DT >= '2012-01-01'
                        AND KYLIN_SALES.PART_DT < '2012-08-01')
;"

Step2: 構建字典

Kylin 使用字典編碼(Dictionary-coder)對 Cube 中的維度值進行壓縮:

  • 緯度值 -> ID 及 ID -> 維度值。 通過存儲 ID 而不是實際值,Cube 的大小會顯著減小
  • ID 保留值的排序,加速了區間(range)查詢
  • 減少了內存和存儲的占用

對于每一個維度列,都會寫入兩個文件:

  • 維度列 distinct 值
  • 字典文件

維度列 distinct 值文件:寫出路徑為 ${baseDir}/${colName}/${colName}.dci-r-${colIndex},如

hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.OPS_USER_ID/OPS_USER_ID.dci-r-00004
hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.SLR_SEGMENT_CD/SLR_SEGMENT_CD.dci-r-00003
hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.LEAF_CATEG_ID/LEAF_CATEG_ID.dci-r-00001

其內容為該維度列的所有 distinct 值,如:

$ hdfs dfs -cat hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.OPS_USER_ID/OPS_USER_ID.dci-r-00004
ADMIN
MODELER

$ hdfs dfs -cat hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.SLR_SEGMENT_CD/SLR_SEGMENT_CD.dci-r-00003
-99
16

$ hdfs dfs -cat hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.LEAF_CATEG_ID/LEAF_CATEG_ID.dci-r-00001
65
175750

字典文件:寫入路徑為 ${baseDir}/${colName}/${colName}.rldict-r-${colIndex},如:

hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.OPS_USER_ID/OPS_USER_ID.rldict-r-00004
hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.SLR_SEGMENT_CD/SLR_SEGMENT_CD.rldict-r-00003
hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/fact_distinct_columns/KYLIN_SALES.LEAF_CATEG_ID/LEAF_CATEG_ID.rldict-r-00001

Step3: 構建 Cube

使用逐層算法(Layer Cubing)

一個N維的Cube,是由:

  • 1個N維子立方體
  • N個(N-1)維子立方體
  • N*(N-1)/2個(N-2)維子立方體
  • (N-2)*(N-3)/2個(N-3)維子立方體
  • ……
  • N個1維子立方體
  • 1個0維子立方體
    構成,總共有2^N個 Cuboid 組成

在逐層算法中,按維度數逐層減少來計算,每個層級的計算(除了第一層,它是從原始數據聚合而來),是基于它上一層級的結果來計算的。比如,[Group by A, B]的結果,可以基于[Group by A, B, C]的結果,通過去掉C后聚合得來的;這樣可以減少重復計算;當 0 維度Cuboid計算出來的時候,整個Cube的計算也就完成了

在介紹如何用 Spark 計算 Cube 之前,讓我們看看 Kylin 如何用 MR 做到這一點;圖1說明了如何使用經典的“逐層”算法計算四維立方體:第一輪MR從源數據聚合基礎(4-D)立方體;第二個MR聚集在基本立方體上以獲得三維立方體;使用N + 1輪MR計算所有層的立方體。


逐層構建將一項大任務劃分為幾個步驟,每個步驟都基于前一步驟的輸出,因此它可以重復使用先前的計算,并且還可以避免在兩者之間出現故障時從頭開始計算。這使它成為一種可靠的算法。

使用 Spark 逐層構建算法:

  • 核心概念和邏輯與MR相同
  • 區別在于將每層的立方體抽象為 RDD,然后使用父 RDD 生成子 RDD。 盡可能在內存中緩存父 RDD 以獲得更好的性能

我們可以在一個 Spark App 中組合所有 map-reduce 步驟;Spark 將生成 DAG 執行計劃,然后自動運行它們。這樣具有更少的調度開銷。

使用 Spark相比于 MR 的耗時比較如下:


構建 Cube 的 Spark 任務如下:

Running org.apache.kylin.engine.spark.SparkCubingByLayer 
-hiveTable default.kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3 
-output hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/ 
-input hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/kylin_intermediate_test_cube_44e5fcfe_e62f_375c_1e91_1d75d2fc6de3 -segmentId 44e5fcfe-e62f-375c-1e91-1d75d2fc6de3 
-metaUrl kylin_metadata@hdfs,path=hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/metadata 
-cubename test_cube

如下為生成的各級維度的 Cuboid 文件:

$ hdfs dfs -ls hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/ 

drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_1_cuboid
drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_2_cuboid
drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_3_cuboid
drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_4_cuboid
drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_5_cuboid
drwxr-xr-x   - root supergroup          0 2019-04-17 08:26 hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/level_base_cuboid

Step4:將 Cuboid 數據轉化為 HFile 文件(By Spark)

一個轉換任務的例子如下;

Running org.apache.kylin.storage.hbase.steps.SparkCubeHFile 
-partitions hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/rowkey_stats/part-r-00000_hfile 
-counterOutput hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/counter 
-cubename test_cube -output hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/hfile 
-input hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/cuboid/ 
-segmentId 44e5fcfe-e62f-375c-1e91-1d75d2fc6de3 
-metaUrl kylin_metadata@hdfs,path=hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/metadata 
-hbaseConfPath hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/hbase-conf.xml

Step5: 將 HFile 文件 load 到 HBase 表中

Version:1.0 StartHTML:0000000100 EndHTML:0000000444 StartFragment:0000000100 EndFragment:0000000444
 -input hdfs://localhost:9000/kylin/kylin_metadata/kylin-f7a8f547-4312-2393-d8ca-9dfd1b6a9bb9/test_cube/hfile -htablename KYLIN_ABHS9OKHZA -cubename test_cube

如下是一個 Cuboid 在 HBase 表中的形式


3.2、增量構建 VS 全量構建

全量構建和增量構建對比如下


  • 對于小數據量的Cube,或者經常需要全表更新的Cube,使用全量構建需要更少的運維精力,以少量的重復計算降低生產環境中的維護復雜度
  • 對于大數據量的Cube,例如,對于一個包含兩年歷史數據的 Cube,如果需要每天更新,那么每天為了新數據而去重復計算過去兩年的數據就會變得非常浪費,在這種情況下需要考慮使用增量構建

3.3、增量構建

Segment在增量構建中,將 Cube 劃分為多個 Segment,每個 Segment 用起始時間和結束時間標志。Segment 代表一段時間內源數據的預計算結果。一個 Segment 的起始時間等于它之前那個 Segment 的結束時間(前閉后開),同理,它的結束時間等于它后面那個 Segment 的起始時間。同一個 Cube 下不同的 Segment 的結構定義、構建過程、優化方法、存儲方式等完全相同。

前提并非所有的 Cube 都適用于增量構建,Cube 的定義必須包含一個時間維度,用來分割不同的 Segment,該維度稱為分割時間列。同一個 Model 下不同 Cube 的分割時間列應該是相同的,因此在 Kylin 中將分割時間列的定義放到了 Model 中。

Cube 的配置Cube 每次增量構建都會生成一個 Segment,隨著時間的推移,當前 Cube 會存在大量的 Segments,這時候會產生以下兩個問題:

  • 執行查詢時查詢引擎要聚合多個 Segments 的結果才能返回正確的查詢結果,聚合的 Segments 越多,查詢的性能越差
  • 每個 Segments 都對應 Hbase 的一張表,過多的 Segments 會在底層的存儲系統產生大量的文件,會給存儲系統 HDFS NameNode 帶來壓力

我們要在 Cube 層面進行以下設置來讓 Kylin 按照一定的規則自動合并 Segments:

  • Partition Start Date:指 Cube 默認的第一個 Segment 的起始時間。同一個 Model 下不同的 Cube 可以指定不同的起始時間
  • Auto Merge Thresholds:用于指定 Segment 自動合并的閾值,將在后文詳述
  • Retention Threshold:保留最近設置閾值的 cube segments 個數,默認是0,它會保留所有歷史構建的segments

觸發增量構建在進行增量構建時,將增量部分的起始時間和結束時間作為增量構建的一部分提交給 Kylin 的任務引擎,任務引擎會根據起始時間和結束時間從 Hive 中抽取相應時間的數據,并對這部分數據做預計算處理,然后將預計算的結果封裝為一個新的 Segment,并將相應的信息保存到元數據和存儲引擎中。

當我們為一個已經有 Segment 的 Cube 觸發增量構建的時候,起始時間的值已經被確定,不能被修改。如果 Cube 中不存在任何的 Segment,那么 Start Date 的值會被設置為 Partition Start Date (在 Model 中設定)。

僅當 Cube 中不存在任何 Segment,或者不存在任何未完成的構建任務時,Kylin 才接受 Cube 上新的構建任務。未完成的構建任務不僅包含正在運行中的構建任務,還包括已經出錯并處于 ERROR 的任務。

Kylin 提供 Rest API 以幫助自動化地觸發增量構建:Build cube

管理 Cube 碎片(Segments)Auto Merge Thresholds 允許用戶設置幾個層級的時間閾值,層級約靠后,時間閾值就越大。舉例來說,[7days, 28days] 這個層級,每當 Cube 中有新的 Segment 生成時,就會觸發一次自動合并的嘗試:

  • 首先查看是否能將連續的若干個 Segments 合并成為一個超過 28 天的大 Segment。在挑選連續 Segments 的過程中:
    • 如果遇到已經有個別 Segment 的時間長度已經超過 28 天,那么系統會跳過該 Segment,從它之后的所有 Segment 中挑選連續的積累超過 28 天的 Segment
    • 如果滿足條件的連續 Segments 還不能夠積累超過 28 天,則系統會使用下一個層級的時間閾值重復尋找過程

四、查詢

4.1、使用標準 SQL 查詢

Kylin 的查詢語言的標準 SQL 的 SELECT 語句(僅支持 SELECT,其他 DDL、DML 均不支持),這是為了獲得與大多數 BI 系統和工具無縫集成,比如下面是一個典型的查詢 SQL:

SELECT DIM1, DIM2, ..., MEASURE1, MEASURE2 ... FROM FACT_TABLE
    INNER JOIN LOOKUP_1 ON FACT_TABLE.FK1 = LOOKUP_1.PK
    INNER JOIN LOOKUP_2 ON FACT_TABLE.FK2 = LOOKUP_2.PK
WHERE FACT_TABLE.DIMN = '' AND ...
    GROUP BY DIM1, DIM2 ...

需要了解的是 ,只有當查詢的模式跟 Cube 定義相匹配的時候,Kylin 才能夠使用 Cube 的數據來完成查詢,匹配的條件如下:

  • Group By 的列和 Where 條件里的列,必須是在 Dimension 中定義的列
  • SQL 中的度量,應該是 Cube 中定義的度量的或是其子集

在一個項目下,如果有多個基于同一模型的 Cube,而且它們都滿足對表、維度和度量的要求;那么,Kylin 會挑選一個 “最優的” 的 Cube 進行查詢;這是一種基于成本(cost)的選擇,成本計算會考慮:

  • Cube 的維度數
  • 度量
  • 數據模型的復雜度

4.2、查詢接入方式

4.2.1、RESTful API

Kylin 提供的主要的 RESTful APIs 如下:

4.2.2、JDBC及其他連接方式

JDBC 連接 url 格式:

jdbc:kylin://<hostname>:<port>/<kylin_project_name>
  • 如果“ssl”為true,“port”應該是Kylin server的HTTPS端口。
  • 如果“port”未被指定,driver會使用默認的端口:HTTP 80,HTTPS 443。
  • 必須指定“kylin_project_name”并且用戶需要確保它在Kylin server上存在。

使用Statement查詢:

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://localhost:7070/kylin_project_name", info);
Statement state = conn.createStatement();
ResultSet resultSet = state.executeQuery("select * from test_table");

while (resultSet.next()) {
    assertEquals("foo", resultSet.getString(1));
    assertEquals("bar", resultSet.getString(2));
    assertEquals("tool", resultSet.getString(3));
}

另外,Kylin 也支持通過 ODBC 及其他 BI 工具(如Tableau、Superset等)進行連接查詢,這最終都是基于 RESTful API 進行查詢的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容