天天看點

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 SQL的底層編譯過程詳解

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的底層編譯過程詳解

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組成。

基本的操作符包括:

  • TableScanOperator
  • SelectOperator
  • FilterOperator
  • JoinOperator
  • GroupByOperator
  • ReduceSinkOperator`

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

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

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

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

  • 投影修剪
  • 推導傳遞謂詞
  • 謂詞下推
  • 将Select-Select,Filter-Filter合并為單個操作
  • 多路 Join
  • 查詢重寫以适應某些列值的Join傾斜

階段五:生成實體執行計劃

生成實體執行計劃即是将邏輯執行計劃生成的OperatorTree轉化為MapReduce Job的過程,主要分為下面幾個階段:

  1. 對輸出表生成MoveTask
  2. 從OperatorTree的其中一個根節點向下深度優先周遊
  3. ReduceSinkOperator标示Map/Reduce的界限,多個Job間的界限
  4. 周遊其他根節點,遇過碰到JoinOperator合并MapReduceTask
  5. 生成StatTask更新中繼資料
  6. 剪斷Map與Reduce間的Operator的關系

階段六:優化實體執行計劃

Hive中的實體優化可以大緻分為以下幾類:

  • 分區修剪(Partition Pruning)
  • 基于分區和桶的掃描修剪(Scan pruning)
  • 如果查詢基于抽樣,則掃描修剪
  • 在某些情況下,在 map 端應用 Group By
  • 在 mapper 上執行 Join
  • 優化 Union,使Union隻在 map 端執行
  • 在多路 Join 中,根據使用者提示決定最後流哪個表
  • 删除不必要的 ReduceSinkOperators
  • 對于帶有Limit子句的查詢,減少需要為該表掃描的檔案數
  • 對于帶有Limit子句的查詢,通過限制 ReduceSinkOperator 生成的内容來限制來自 mapper 的輸出
  • 減少使用者送出的SQL查詢所需的Tez作業數量
  • 如果是簡單的提取查詢,避免使用MapReduce作業
  • 對于帶有聚合的簡單擷取查詢,執行不帶 MapReduce 任務的聚合
  • 重寫 Group By 查詢使用索引表代替原來的表
  • 當表掃描之上的謂詞是相等謂詞且謂詞中的列具有索引時,使用索引掃描

經過以上六個階段,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的過程如下:

Hive SQL的底層編譯過程詳解

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的過程如下:

Hive SQL的底層編譯過程詳解

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即可完成去重:

Hive SQL的底層編譯過程詳解

MapReduce Distinct的實作

本文來自微信公衆号:五分鐘學大資料,轉載請在公衆号背景擷取作者微信進行授權