Hive SQL 底層執行過程詳細剖析(好文收藏)

本文結構採用宏觀着眼,微觀入手,從整體到細節的方式剖析 Hive SQL 底層原理。第一節先介紹 Hive 底層的整體執行流程,然後第二節介紹執行流程中的 SQL 編譯成 MapReduce 的過程,第三節剖析 SQL 編譯成 MapReduce 的具體實現原理。

Hive

Hive 是什麼?Hive 是數據倉庫工具,再具體點就是一個 SQL 解析引擎,因爲它即不負責存儲數據,也不負責計算數據,只負責解析 SQL,記錄元數據。

Hive 直接訪問存儲在 HDFS 中或者 HBase 中的文件,通過 MapReduce、Spark 或 Tez 執行查詢。

我們今天來聊的就是 Hive 底層是怎樣將我們寫的 SQL 轉化爲 MapReduce 等計算引擎可識別的程序。瞭解 Hive SQL 的底層編譯過程有利於我們優化 Hive SQL,提升我們對 Hive 的掌控力,同時有能力去定製一些需要的功能。

Hive 底層執行架構

我們先來看下 Hive 的底層執行架構圖, Hive 的主要組件與 Hadoop 交互的過程:

Hive 底層執行架構

在 Hive 這一側,總共有五個組件:

  1. UI:用戶界面。可看作我們提交 SQL 語句的命令行界面。

  2. DRIVER:驅動程序。接收查詢的組件。該組件實現了會話句柄的概念。

  3. COMPILER:編譯器。負責將 SQL 轉化爲平臺可執行的執行計劃。對不同的查詢塊和查詢表達式進行語義分析,並最終藉助表和從 metastore 查找的分區元數據來生成執行計劃。

  4. METASTORE:元數據庫。存儲 Hive 中各種表和分區的所有結構信息。

  5. EXECUTION ENGINE:執行引擎。負責提交 COMPILER 階段編譯好的執行計劃到不同的平臺上。

上圖的基本流程是:

步驟 1:UI 調用 DRIVER 的接口;

步驟 2:DRIVER 爲查詢創建會話句柄,並將查詢發送到 COMPILER(編譯器) 生成執行計劃;

步驟 3 和 4:編譯器從元數據存儲中獲取本次查詢所需要的元數據,該元數據用於對查詢樹中的表達式進行類型檢查,以及基於查詢謂詞修建分區;

步驟 5:編譯器生成的計劃是分階段的 DAG,每個階段要麼是 map/reduce 作業,要麼是一個元數據或者 HDFS 上的操作。將生成的計劃發給 DRIVER。

如果是 map/reduce 作業,該計劃包括 map operator trees 和一個  reduce operator tree,執行引擎將會把這些作業發送給 MapReduce :

步驟 6、6.1、6.2 和 6.3:執行引擎將這些階段提交給適當的組件。在每個 task(mapper/reducer) 中,從 HDFS 文件中讀取與表或中間輸出相關聯的數據,並通過相關算子樹傳遞這些數據。最終這些數據通過序列化器寫入到一個臨時 HDFS 文件中(如果不需要 reduce 階段,則在 map 中操作)。臨時文件用於向計劃中後面的 map/reduce 階段提供數據。

步驟 7、8 和 9:最終的臨時文件將移動到表的位置,確保不讀取髒數據 (文件重命名在 HDFS 中是原子操作)。對於用戶的查詢,臨時文件的內容由執行引擎直接從 HDFS 讀取,然後通過 Driver 發送到 UI。

Hive SQL 編譯成 MapReduce 過程

編譯 SQL 的任務是在上節中介紹的 COMPILER(編譯器組件)中完成的。Hive 將 SQL 轉化爲 MapReduce 任務,整個編譯過程分爲六個階段:

Hive SQL 編譯過程

  1. 詞法、語法解析: Antlr 定義 SQL 的語法規則,完成 SQL 詞法,語法解析,將 SQL 轉化爲抽象語法樹 AST Tree;

Antlr 是一種語言識別的工具,可以用來構造領域語言。使用 Antlr 構造特定的語言只需要編寫一個語法文件,定義詞法和語法替換規則即可,Antlr 完成了詞法分析、語法分析、語義分析、中間代碼生成的過程。

  1. 語義解析: 遍歷 AST Tree,抽象出查詢的基本組成單元 QueryBlock;

  2. 生成邏輯執行計劃: 遍歷 QueryBlock,翻譯爲執行操作樹 OperatorTree;

  3. 優化邏輯執行計劃: 邏輯層優化器進行 OperatorTree 變換,合併 Operator,達到減少 MapReduce Job,減少數據傳輸及 shuffle 數據量;

  4. 生成物理執行計劃: 遍歷 OperatorTree,翻譯爲 MapReduce 任務;

  5. 優化物理執行計劃: 物理層優化器進行 MapReduce 任務的變換,生成最終的執行計劃。

下面對這六個階段詳細解析:

爲便於理解,我們拿一個簡單的查詢語句進行展示,對 5 月 23 號的地區維表進行查詢:

select * from dim.dim_region where dt = '2021-05-23';

階段一:詞法、語法解析

根據 Antlr 定義的 sql 語法規則,將相關 sql 進行詞法、語法解析,轉化爲抽象語法樹 AST Tree:

ABSTRACT SYNTAX TREE:
TOK_QUERY
    TOK_FROM 
    TOK_TABREF
           TOK_TABNAME
               dim
                 dim_region
    TOK_INSERT
      TOK_DESTINATION
          TOK_DIR
              TOK_TMP_FILE
        TOK_SELECT
          TOK_SELEXPR
              TOK_ALLCOLREF
        TOK_WHERE
          =
              TOK_TABLE_OR_COL
                  dt
                    '2021-05-23'

階段二:語義解析

遍歷 AST Tree,抽象出查詢的基本組成單元 QueryBlock:

AST Tree 生成後由於其複雜度依舊較高,不便於翻譯爲 mapreduce 程序,需要進行進一步抽象和結構化,形成 QueryBlock。

QueryBlock 是一條 SQL 最基本的組成單元,包括三個部分:輸入源,計算過程,輸出。簡單來講一個 QueryBlock 就是一個子查詢。

QueryBlock 的生成過程爲一個遞歸過程,先序遍歷 AST Tree ,遇到不同的 Token 節點 (理解爲特殊標記),保存到相應的屬性中。

階段三:生成邏輯執行計劃

遍歷 QueryBlock,翻譯爲執行操作樹 OperatorTree:

Hive 最終生成的 MapReduce 任務,Map 階段和 Reduce 階段均由 OperatorTree 組成。

基本的操作符包括:

Operator 在 Map Reduce 階段之間的數據傳遞都是一個流式的過程。每一個 Operator 對一行數據完成操作後之後將數據傳遞給 childOperator 計算。

由於 Join/GroupBy/OrderBy 均需要在 Reduce 階段完成,所以在生成相應操作的 Operator 之前都會先生成一個 ReduceSinkOperator,將字段組合並序列化爲 Reduce Key/value, Partition Key。

階段四:優化邏輯執行計劃

Hive 中的邏輯查詢優化可以大致分爲以下幾類:

階段五:生成物理執行計劃

生成物理執行計劃即是將邏輯執行計劃生成的 OperatorTree 轉化爲 MapReduce Job 的過程,主要分爲下面幾個階段:

  1. 對輸出表生成 MoveTask

  2. 從 OperatorTree 的其中一個根節點向下深度優先遍歷

  3. ReduceSinkOperator 標示 Map/Reduce 的界限,多個 Job 間的界限

  4. 遍歷其他根節點,遇過碰到 JoinOperator 合併 MapReduceTask

  5. 生成 StatTask 更新元數據

  6. 剪斷 Map 與 Reduce 間的 Operator 的關係

階段六:優化物理執行計劃

Hive 中的物理優化可以大致分爲以下幾類:

經過以上六個階段,SQL 就被解析映射成了集羣上的 MapReduce 任務。

SQL 編譯成 MapReduce 具體原理

在階段五 - 生成物理執行計劃,即遍歷 OperatorTree,翻譯爲 MapReduce 任務,這個過程具體是怎麼轉化的呢

我們接下來舉幾個常用 SQL 語句轉化爲 MapReduce 的具體步驟:

Join 的實現原理

以下面這個 SQL 爲例,講解 join 的實現:

select u.name, o.orderid from order o join user u on o.uid = u.uid;

在 map 的輸出 value 中爲不同表的數據打上 tag 標記,在 reduce 階段根據 tag 判斷數據來源。MapReduce 的過程如下:

MapReduce CommonJoin 的實現

Group By 的實現原理

以下面這個 SQL 爲例,講解 group by 的實現:

select rank, isonline, count(*) from city group by rank, isonline;

將 GroupBy 的字段組合爲 map 的輸出 key 值,利用 MapReduce 的排序,在 reduce 階段保存 LastKey 區分不同的 key。MapReduce 的過程如下:

MapReduce Group By 的實現

Distinct 的實現原理

以下面這個 SQL 爲例,講解 distinct 的實現:

select dealid, count(distinct uid) num from order group by dealid;

當只有一個 distinct 字段時,如果不考慮 Map 階段的 Hash GroupBy,只需要將 GroupBy 字段和 Distinct 字段組合爲 map 輸出 key,利用 mapreduce 的排序,同時將 GroupBy 字段作爲 reduce 的 key,在 reduce 階段保存 LastKey 即可完成去重:

MapReduce Distinct 的實現

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7yffuvCr17naOS7GeV8tTQ