5000 字帶你快速入門 Apache Kylin

語言: CN / TW / HK

本文介紹 Cube(多維立方體)思想,以及圍繞此思想,Apache Kylin 如何將某些場景下的大資料 SQL 查詢速度提升到亞秒級別。

01

什麼是 Kylin

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

Apache Kylin 也是中國人主導的,第一個 Apache 頂級開源專案,在開源社群有較大影響力。

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

  • 大資料查詢要的一般是統計結果,是多條記錄經過聚合函式計算後的統計值

    • 原始的記錄則不是必需的,或者訪問頻率和概率都極低

  • 聚合是按維度進行的,有意義的維度聚合組合也是相對有限的,一般不會隨著資料的膨脹而膨脹

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

02

定義 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": []

}

03

構建 Cube

以使用 Spark 構建 Cube 為例

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

  • 全量構建:構建時讀取的資料來源是全集

  • 增量構建:構建時讀取的資料來源是子集

3.1、構建流程

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

Step 1:建立 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')

;"

Step 2:構建字典

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

Step 3:構建 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

Step 4:將 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

Step 5: 將 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(http://kylin.apache.org/cn/docs/howto/howto_use_restapi.html#build-cube)

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

  • 首先檢視是否能將連續的若干個 Segments 合併成為一個超過 28 天的大 Segment。在挑選連續 Segments 的過程中:

    • 如果遇到已經有個別 Segment 的時間長度已經超過 28 天,那麼系統會跳過該 Segment,從它之後的所有 Segment 中挑選連續的積累超過 28 天的 Segment

    • 如果滿足條件的連續 Segments 還不能夠積累超過 28 天,則系統會使用下一個層級的時間閾值重複尋找過程

04

查詢

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 如下:

  • Query

    • Authentication

    • Query

    • List queryable tables

  • CUBE

    • List cubes

    • Get cube

    • Get cube descriptor (dimension, measure info, etc)

    • Get data model (fact and lookup table info)

    • Build cube

    • Enable cube

    • Disable cube

    • Purge cube

    • Delete segment

  • JOB

    • Resume job

    • Pause job

    • Drop job

    • Discard job

    • Get job status

    • Get job step output

    • Get job list

  • Metadata

    • Get Hive Table

    • Get Hive Tables

    • Load Hive Tables

  • Cache

    • Wipe cache

  • Streaming

    • Initiate cube start position

    • Build stream cube

    • Check segment holes

    • Fill segment holes

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 進行查詢的。

05

參考

  • 《Apache Kylin 權威指南》

  • http://kylin.apache.org/cn/docs/

  • https://blog.csdn.net/bbbeoy/article/details/79073725

  • https://www.slidestalk.com/x/241/kyligence_open_class

  • http://bigdata-star.com/archives/2023

  • https://www.jianshu.com/p/befc0030afdc

往期案例與實踐

Kylin's Github Repo 傳送門

↓↓↓

https://github.com/apache/kylin

喜歡:heart:Kylin 的話,別忘了 Star :star2:一下喲~