Apache Kylin 概覽

擴(kuò)展閱讀:

一、什么是 Kylin

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

Apache Kylin 也是中國人主導(dǎo)的,第一個 Apche 頂級開源項(xiàng)目,在開源社區(qū)有較大影響力。

Kylin 對于解決的問題有以下假設(shè):

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

基于以上兩點(diǎn),可以得到一個新的思路—預(yù)計(jì)算,應(yīng)盡量多地預(yù)先計(jì)算聚合結(jié)果,在查詢時(shí)應(yīng)該盡量利用預(yù)計(jì)算的結(jié)果得出查詢結(jié)果,從而避免直接掃描可能無限增大的原始記錄

二、定義 Cube

2.1、什么是 Cube

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

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

2.2、創(chuàng)建數(shù)據(jù)模型

2.2.1、數(shù)據(jù)模型

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

雪花模型:如果將星型模型中某些維度的表再做規(guī)范,抽取成更細(xì)的維度表,然后讓維度表之間也進(jìn)行關(guān)聯(lián),那么這種模型成為雪花模型(雪花模型可以通過一定的轉(zhuǎn)換,變?yōu)樾切湍P停?br>

2.2.2、創(chuàng)建模型

  • Model 是 Cube 的基礎(chǔ),用于描述一個數(shù)據(jù)模型
  • 有了數(shù)據(jù)模型,定義 Cube 可以直接從此模型定義的表和列中進(jìn)行選擇
  • 基于一個數(shù)據(jù)模型可以創(chuàng)建多個 Cube

數(shù)據(jù)模型可用一個 json 表示,如下是一個例子:

{
  "name": "test_model",                 // 名為 test_model 的數(shù)據(jù)模型
  "fact_table": "DEFAULT.KYLIN_SALES",  // 事實(shí)表為 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": [                       // 定義維度,維度可以來自于事實(shí)表和維表;后續(xù)基于該模型的 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": [                        // 定義度量,度量智能來自事實(shí)表;后續(xù)基于該模型的 Cube 的度量只能從這里定義的 metrics 中選
    "KYLIN_SALES.PRICE",
    "KYLIN_SALES.ITEM_COUNT",
    "KYLIN_SALES.SELLER_ID"
  ],
  "filter_condition": "price > 0",    // 定義向數(shù)據(jù)源查詢數(shù)據(jù)時(shí)會帶上的過濾條件
  "partition_desc": {                 // 指定 KYLIN_SALES.PART_DT 列作為模型的分割時(shí)間列,以支持基于該模型的 Cube 按此列做增量構(gòu)建
    "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、創(chuàng)建 Cube

高級設(shè)置的一些說明:

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

2.3.1、一個 Cube 例子

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

{
  "name": "test_cube",
  "model_name": "test_model",     // 使用名為 model_test 的數(shù)據(jù)模型
  "description": "",
  "null_string": null,
  "dimensions": [                 // 維度,可以來自事實(shí)表或維度表
    {
      "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": [                 // 度量,即哪個列做什么聚合計(jì)算
    {
      "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 配置,主要關(guān)注維度列在 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 日期/時(shí)間 分區(qū)起始值
  "partition_date_end": 3153600000000,  // Cube 日期/時(shí)間 分區(qū)結(jié)束值
  "auto_merge_time_ranges": [           // 自動合并小的 segments 到中等甚至更大的 segment
    604800000,
    2419200000
  ],
  "retention_range": 0,                 // 不刪除舊的 Cube Segment
  "engine_type": 4,                     // 構(gòu)建 Cube 的引擎為 Spark
  "storage_type": 2,                    // 使用 Hbase 存儲 Cube
  "override_kylin_properties": {},
  "cuboid_black_list": []
}

三、構(gòu)建 Cube

以使用 Spark 構(gòu)建 Cube 為例

新創(chuàng)建的 Cube 只有定義,而沒有計(jì)算的數(shù)據(jù),它的狀態(tài)是 “DISABLED” 的,要想讓 Cube 有數(shù)據(jù),還需要對它進(jìn)行構(gòu)建,Cube 的構(gòu)建方式通常有兩種:

  • 全量構(gòu)建:構(gòu)建時(shí)讀取的數(shù)據(jù)源是全集
  • 增量構(gòu)建:構(gòu)建時(shí)讀取的數(shù)據(jù)源是子集

3.1、構(gòu)建流程

以全量構(gòu)建為例,Cube 的構(gòu)建主要包含以下步驟,由構(gòu)建引擎來調(diào)度執(zhí)行:


Step1: 創(chuàng)建 Hive 大平表

將創(chuàng)建 Cube 涉及到的維度從原有的事實(shí)表和維度表中查詢出來組成一條完整的數(shù)據(jù)插入到一個新的 hive 表中

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

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');

-- 根據(jù)數(shù)據(jù)模型定義的 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: 構(gòu)建字典

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

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

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

  • 維度列 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

其內(nèi)容為該維度列的所有 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: 構(gòu)建 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維子立方體
    構(gòu)成,總共有2^N個 Cuboid 組成

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

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


逐層構(gòu)建將一項(xiàng)大任務(wù)劃分為幾個步驟,每個步驟都基于前一步驟的輸出,因此它可以重復(fù)使用先前的計(jì)算,并且還可以避免在兩者之間出現(xiàn)故障時(shí)從頭開始計(jì)算。這使它成為一種可靠的算法。

使用 Spark 逐層構(gòu)建算法:

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

我們可以在一個 Spark App 中組合所有 map-reduce 步驟;Spark 將生成 DAG 執(zhí)行計(jì)劃,然后自動運(yùn)行它們。這樣具有更少的調(diào)度開銷。

使用 Spark相比于 MR 的耗時(shí)比較如下:


構(gòu)建 Cube 的 Spark 任務(wù)如下:

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 數(shù)據(jù)轉(zhuǎn)化為 HFile 文件(By Spark)

一個轉(zhuǎn)換任務(wù)的例子如下;

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、增量構(gòu)建 VS 全量構(gòu)建

全量構(gòu)建和增量構(gòu)建對比如下


  • 對于小數(shù)據(jù)量的Cube,或者經(jīng)常需要全表更新的Cube,使用全量構(gòu)建需要更少的運(yùn)維精力,以少量的重復(fù)計(jì)算降低生產(chǎn)環(huán)境中的維護(hù)復(fù)雜度
  • 對于大數(shù)據(jù)量的Cube,例如,對于一個包含兩年歷史數(shù)據(jù)的 Cube,如果需要每天更新,那么每天為了新數(shù)據(jù)而去重復(fù)計(jì)算過去兩年的數(shù)據(jù)就會變得非常浪費(fèi),在這種情況下需要考慮使用增量構(gòu)建

3.3、增量構(gòu)建

Segment在增量構(gòu)建中,將 Cube 劃分為多個 Segment,每個 Segment 用起始時(shí)間和結(jié)束時(shí)間標(biāo)志。Segment 代表一段時(shí)間內(nèi)源數(shù)據(jù)的預(yù)計(jì)算結(jié)果。一個 Segment 的起始時(shí)間等于它之前那個 Segment 的結(jié)束時(shí)間(前閉后開),同理,它的結(jié)束時(shí)間等于它后面那個 Segment 的起始時(shí)間。同一個 Cube 下不同的 Segment 的結(jié)構(gòu)定義、構(gòu)建過程、優(yōu)化方法、存儲方式等完全相同。

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

Cube 的配置Cube 每次增量構(gòu)建都會生成一個 Segment,隨著時(shí)間的推移,當(dāng)前 Cube 會存在大量的 Segments,這時(shí)候會產(chǎn)生以下兩個問題:

  • 執(zhí)行查詢時(shí)查詢引擎要聚合多個 Segments 的結(jié)果才能返回正確的查詢結(jié)果,聚合的 Segments 越多,查詢的性能越差
  • 每個 Segments 都對應(yīng) Hbase 的一張表,過多的 Segments 會在底層的存儲系統(tǒng)產(chǎn)生大量的文件,會給存儲系統(tǒng) HDFS NameNode 帶來壓力

我們要在 Cube 層面進(jìn)行以下設(shè)置來讓 Kylin 按照一定的規(guī)則自動合并 Segments:

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

觸發(fā)增量構(gòu)建在進(jìn)行增量構(gòu)建時(shí),將增量部分的起始時(shí)間和結(jié)束時(shí)間作為增量構(gòu)建的一部分提交給 Kylin 的任務(wù)引擎,任務(wù)引擎會根據(jù)起始時(shí)間和結(jié)束時(shí)間從 Hive 中抽取相應(yīng)時(shí)間的數(shù)據(jù),并對這部分?jǐn)?shù)據(jù)做預(yù)計(jì)算處理,然后將預(yù)計(jì)算的結(jié)果封裝為一個新的 Segment,并將相應(yīng)的信息保存到元數(shù)據(jù)和存儲引擎中。

當(dāng)我們?yōu)橐粋€已經(jīng)有 Segment 的 Cube 觸發(fā)增量構(gòu)建的時(shí)候,起始時(shí)間的值已經(jīng)被確定,不能被修改。如果 Cube 中不存在任何的 Segment,那么 Start Date 的值會被設(shè)置為 Partition Start Date (在 Model 中設(shè)定)。

僅當(dāng) Cube 中不存在任何 Segment,或者不存在任何未完成的構(gòu)建任務(wù)時(shí),Kylin 才接受 Cube 上新的構(gòu)建任務(wù)。未完成的構(gòu)建任務(wù)不僅包含正在運(yùn)行中的構(gòu)建任務(wù),還包括已經(jīng)出錯并處于 ERROR 的任務(wù)。

Kylin 提供 Rest API 以幫助自動化地觸發(fā)增量構(gòu)建:Build cube

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

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

四、查詢

4.1、使用標(biāo)準(zhǔn) SQL 查詢

Kylin 的查詢語言的標(biāo)準(zhǔn) SQL 的 SELECT 語句(僅支持 SELECT,其他 DDL、DML 均不支持),這是為了獲得與大多數(shù) BI 系統(tǒng)和工具無縫集成,比如下面是一個典型的查詢 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 ...

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

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

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

  • Cube 的維度數(shù)
  • 度量
  • 數(shù)據(jù)模型的復(fù)雜度

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”應(yīng)該是Kylin server的HTTPS端口。
  • 如果“port”未被指定,driver會使用默認(rèn)的端口: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等)進(jìn)行連接查詢,這最終都是基于 RESTful API 進(jìn)行查詢的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容