1. Hive的架構
Hive的體系結構可以分為以下幾部分:
- 用戶接口主要有三個:CLI,JDBC/ODBC和 Web UI。
- ①其中,最常用的是CLI,即Shell命令行;
- ②JDBC/ODBC Client是Hive的Java客戶端,與使用傳統數據庫JDBC的方式類似,用戶需要連接至Hive Server;
- ③Web UI是通過瀏覽器訪問。
- Hive將元數據存儲在數據庫中,如mysql、derby。Hive中的元數據包括表的名字,表的列和分區及其屬性,表的屬性(是否為外部表等),表的數據所在目錄等。
- 解釋器、編譯器、優化器完成HQL查詢語句從詞法分析、語法分析、編譯、優化以及查詢計划的生成。生成的查詢計划存儲在HDFS中,並在隨后有MapReduce調用 執行。
- Hive的數據存儲在HDFS中,大部分的查詢、計算由MapReduce完成(包含*的查詢,比如select * from tbl不會生成MapReduce任務)
2. Hive的元數據存儲
對於數據存儲,Hive沒有專門的數據存儲格式,也沒有為數據建立索引,用戶可以非常自由的組織Hive中的表,只需要在創建表的時候告訴Hive數據中的列分隔符和行分隔符,Hive就可以解析數據。Hive中所有的數據都存儲在HDFS中,存儲結構主要包括數據庫、文件、表和視圖。Hive中包含以下數據模型:Table內部表,External Table外部表,Partition分區,Bucket桶。Hive默認可以直接加載文本文件,還支持sequence file、RCFile。
Hive將元數據存儲在RDBMS中,有三種模式可以連接到數據庫:
2.1 元數據內嵌模式(Embedded Metastore Database)
此模式連接到一個本地內嵌In-memory的數據庫Derby,一般用於Unit Test,內嵌的derby數據庫每次只能訪問一個數據文件,也就意味着它不支持多會話連接。
參數 | 描述 | 用例 |
javax.jdo.option.ConnectionURL | JDBC連接url | jdbc:derby:databaseName=metastore_db;create=true |
javax.jdo.option.ConnectionDriverName | JDBC driver名稱 | org.apache.derby.jdbc.EmbeddedDriver |
javax.jdo.option.ConnectionUserName | 用戶名 | xxx |
javax.jdo.option.ConnectionPassword | 密碼 | xxxx |
2.2 本地元數據存儲模式(Local Metastore Server)
通過網絡連接到一個數據庫中,是最經常使用到的模式。
參數 | 描述 | 用例 |
javax.jdo.option.ConnectionURL | JDBC連接url | jdbc:mysql://<host name>/databaseName?createDatabaseIfNotExist=true |
javax.jdo.option.ConnectionDriverName | JDBC driver名稱 | com.mysql.jdbc.Driver |
javax.jdo.option.ConnectionUserName | 用戶名 | xxx |
javax.jdo.option.ConnectionPassword | 密碼 | xxxx |
2.3 遠程訪問元數據模式(Remote Metastore Server)
用於非Java客戶端訪問元數據庫,在服務端啟動MetaServer,客戶端利用Thrift協議通過MetaStoreServer訪問元數據庫。
- 服務端啟動HiveMetaStore
第一種方式:
hive --service metastore -p 9083 &
第二種方式:
如果在hive-site.xml里指定了hive.metastore.uris的port,就可以不指定端口啟動了
<property> <name>hive.metastore.local</name> <value>false</value> </property> <property> <name>hive.metastore.uris</name> <value>thrift://node1:9083</value> </property> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://node1/hive_remote?createDatabaseIfNotExist=true</value> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>com.mysql.jdbc.Driver</value> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>root</value> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>123456</value> </property>
hive --service metastore
- 客戶端配置
參數 | 描述 | 用例 |
hive.metastore.uris | metastore server的url | thrift://<host_name>:9083 |
hive.metastore.local | metastore server的位置 | false表示遠程 |
2.4 三種模式匯總
3. Hive工作原理
3.1 Hive內部組件分布構成
No. 1 Hive全局架構圖
從圖1 Hive全局架構圖中可以看到Hive架構包括如下組件:CLI(command line interface)、JDBC/ODBC、Thrift Server、Hive WEB Interface(HWI)、metastore和Driver(Compiler、Optimizer)
Metastore組件:元數據服務組件,這個組件用於存儲hive的元數據,包括表名、表所屬的數據庫、表的擁有者、列/分區字段、表的類型、表的數據所在目錄等內容。hive的元數據存儲在關系數據庫里,支持derby、mysql兩種關系型數據庫。元數據對於hive十分重要,因此hive支持把metastore服務獨立出來,安裝到遠程的服務器集群里,從而解耦hive服務和metastore服務,保證hive運行的健壯性。
Driver組件:該組件包括Parser、Compiler、Optimizer和Executor,它的作用是將我們寫的HiveQL(類SQL)語句進行解析、編譯、優化,生成執行計划,然后調用底層的mapreduce計算框架。
-
- 解釋器(Parser):將SQL字符串轉化為抽象語法樹AST;
- 編譯器(Compiler):將AST編譯成邏輯執行計划;
- 優化器(Optimizer):對邏輯執行計划進行優化;
- 執行器(Executor):將邏輯執行計划轉成可執行的物理計划,如MR/Spark
CLI:command line interface,命令行接口
ThriftServers:提供JDBC和ODBC接入的能力,它用來進行可擴展且跨語言的服務的開發,hive集成了該服務,能讓不同的編程語言調用hive的接口。
3.2 Hive詳細運行架構
No.2 Hive運行詳細架構圖
工作流程步驟:
- 1. ExecuteQuery(執行查詢操作):命令行或Web UI之類的Hive接口將查詢發送給Driver(任何數據驅動程序,如JDBC、ODBC等)執行;
- 2. GetPlan(獲取計划任務):Driver借助編譯器解析查詢,檢查語法和查詢計划或查詢需求;
- 3. GetMetaData(獲取元數據信息):編譯器將元數據請求發送到Metastore(任何數據庫);
- 4. SendMetaData(發送元數據):MetaStore將元數據作為對編譯器的響應發送出去;
- 5. SendPlan(發送計划任務):編譯器檢查需求並將計划重新發送給Driver。到目前為止,查詢的解析和編譯已經完成;
- 6. ExecutePlan(執行計划任務):Driver將執行計划發送到執行引擎;
- 6.1 ExecuteJob(執行Job任務):在內部,執行任務的過程是MapReduce Job。執行引擎將Job發送到ResourceManager,ResourceManager位於Name節點中,並將job分配給datanode中的NodeManager。在這里,查詢執行MapReduce任務;
- 6.1 Metadata Ops(元數據操作):在執行的同時,執行引擎可以使用Metastore執行元數據操作;
- 6.2 jobDone(完成任務):完成MapReduce Job;
- 6.3 dfs operations(dfs操作記錄):向namenode獲取操作數據;
- 7. FetchResult(拉取結果集):執行引擎將從datanode上獲取結果集;
- 8. SendResults(發送結果集至driver):執行引擎將這些結果值發送給Driver;
- 9. SendResults (driver將result發送至interface):Driver將結果發送到Hive接口(即UI);
3.3 Driver端的Hive編譯流程
Hive是如何將SQL轉化成MapReduce任務的,整個編輯過程分為六個階段:
- 1. 詞法分析/語法分析:使用Antlr定義SQL的語法規則,完成SQL詞法,語法解析,將SQL語句解析成抽象語法樹(AST Tree);
- 2. 語義分析:遍歷AST Tree,抽象出查詢的基本組成單元QueryBlock,並從Metastore獲取模式信息,驗證SQL語句中隊表名、列名,以及數據類型(即QueryBlock)的檢查和隱式轉換,以及Hive提供的函數和用戶自定義的函數(UDF/UAF);
- 3. 邏輯計划生成:遍歷QueryBlock,翻譯生成執行操作樹Operator Tree(即邏輯計划);
- 4. 邏輯計划優化:邏輯層優化器對Operator Tree進行變換優化,合並不必要的ReduceSinkOperator,減少shuffle數據量;
- 5. 物理計划生成:將Operator Tree(邏輯計划)生成包含由MapReduce任務組成的DAG的物理計划——任務樹;
- 6. 物理計划優化:物理層優化器對MapReduce任務樹進行優化,並進行MapReduce任務的變換,生成最終的執行計划;
4. Hive源碼分析
這里以hive-2.3.6為例。
4.1 源碼目錄構成分析
hive的三個重要組成部分
- serde:包含hive內置的序列化解析類,運行用戶自定義序列化和發序列化解析器;
- metastore:hive元數據服務器,用來存放數據倉庫中所有表和分區的信息,hive元數據建表sql;
- ql:解析sql生成的執行計划(了解hive執行流程的核心)
其他
- cli:hive命令行入口;
- common:hive基礎代碼庫,hive各組件信息的傳遞是通過hiveconf類管理的;
- service:所有對外api接口的服務端,可以用於其他客戶端與hive交互,例如:jdbc;
- bin:hive執行的所有腳本;
- beeline:hiveserver2提供的命令行工具;
- findbugs:在java程序中查找bug的程序;
- hwi:hive web頁面的接口;
- shim:用來兼容不容版本的hadoop和hive的版本;
- hcatalog:Apache對於表和底層數據管理統一服務平台,hcatalog底層依賴於hive metastore;
- ant:此組件包含一些ant任務需要的基礎代碼;
輔助組件
- conf:包含hive配置文件,hive-site.xml等;
- data:hive所有的測試數據;
- lib:hive運行所有的依賴包;
4.2 sql編譯代碼流程
對照節點3.3 Driver端的Hive編譯流程,這里是具體的執行過程,如下:
4.3 sql編譯源碼分析
ql文件目錄下面可以找到以下幾個文件:
- HiveLexer.g 是做詞法分析的,定義了所有用到的token;
- HiveParser.g 是做語法解析的;
- FromClauseParser.g from從句語法解析;
- SelectClauseParser.g select從句語法解析;
- IdentifiersParser.g 自定義函數的解析;
hive源碼中語法文件之間的關系:
查看hive源碼Driver類,整個sql語句編譯、執行的步驟,執行的入口方法是在Driver.run(String command)方法中,執行的參數也就是一個sql字符串,只要的方法是:
① Driver類中run(String command)方法調用Driver.runInternal(String command,boolean alreadyCompiled)方法,
② Driver類中,runInternal方法則是調用compileInternal(command,true),
③ Driver類中,compileInternal方法調用compile(command,true,deferClose)方法;這里的compile方法為核心的編譯方法,主要是將sql字符串翻譯成ast樹,然后翻譯成可執行的task樹,然后再優化執行樹,如下圖:
④ Driver類中,complie方法里面調用ParseUtils.parse(command,ctx)方法
⑤ ParserUtils.parse方法則是調用ParseDriver的parser解析方法
可以看出ParseDriver.parse方法獲取AST Tree【抽象語法樹(abstract syntax tree )】信息
⑥ Hive中調用antlr類的代碼org.apache.hadoop.hive.ql.parse.ParseDriver類 返回的HiveParser.statement_return和上面一樣,是棵ast的語法樹,具體語法樹的接口可以參見相應的HiveParse.g文件
⑦ 得到語法樹之后,返回到Driver類中,會根據語法樹根節點的類型來選擇相應的SemanticAnalyzer
紅框② BaseSemanticAnalyzer sem = SemanticAnalyzerFactory.get(queryState, tree); 主要是根據根節點的語法樹類型來選擇相應的analyzer,具體的選擇analyzer代碼如下:
對於DDL操作,得到的就是DDLSemanticAnalyzer,對於一般的insert(hive中存select語句會被翻譯成一個insert tmpDirectory的語句)得到的就是SemanticAnalyzer。
⑧ 接着Driver類中,調用BaseSemanticAnalyzer.analyze(tree,ctx)來將語法樹翻譯成可執行的執行計划;
BaseSemanticAnalyzer的analyzer方法如下:
SemanticAnalyzer繼承BaseSemanticAnalyzer並重寫analyzerInternal(ast)方法,SemanticAnalyzer.java(語義分析器)對一個樹的根節點AST就能對整棵樹進行解析(深度優先探索)
接下來是把抽象語法樹變成一個QB(query block),如下:
一個QB類為:
QB的兩個重要變量是qbp和qbm他們都有QB的引用,這樣組成了一棵樹。
在SemanticAnalyzer.analyzeInternal方法中Operator sinkOp = genOPTree(ast, plannerCtx);我們看一下Operator類的結構:
從代碼中可以看到Operator有很多children和parent,由此這是一個有向無環圖(DAG),QB經過genPlan()方法變成了一個DAG,接下來的Optimizer optm = new Optimizer();是邏輯優化器。調用optm.initialize(conf),Optimizer有以下優化器:
在SemanticAnalyzer.analyzeInternal方法中最終會調用compiler.compile(pCtx,rootTasks,inputs,outputs);把可執行的計划存儲在protected List<Task<? extends Serializable>> rootTasks;屬性中,Task的executeTask()方法是可以直接執行的,最終實際的執行也是調用每個task的executeTask方法,依賴以及調度是在上層控制的,Task的繼承關系如下:
Task是一個樹形結構,每個task有一堆child task,這些child是在執行順序上依賴於自己的task,rootTasks中存儲的就是整個執行計划中需要最開始執行的task list,一棵“倒着的執行依賴樹”。
⑨ Driver類中,執行task,Driver.execute()為入口
將可執行的task放入runnable中,初始為root task list,runnable表示正在運行running的task。
具體的執行流程如下:
- 不斷去遍歷runnable,選出一個執行launchTask(tsk,queryId,noName,running,jobname,jobs,driverCxt),在這個方法中,啟動task,其實就是調用task的executeTask()方法。
這個里面hive是支持並發執行task的,若是需要並發的話每個task被封裝成一個Thread的子類,然后自行啟動。
- 找出執行完成的task,然后遍歷該task的子task,選出可執行(pre task已經執行完)task放入runnable中,然后重復上一步驟。
對於一些有多個pre task的child task,會在最后一個pre task執行完成后被啟動,所以在這會被在child中過掉。
以上邏輯就是整個hivesql的編譯流程代碼的大體脈絡。
4.4 待補充
idea創建AstTreeTest測試類打印ast tree信息
public class AstTreeTest { public static void main(String[] args) { ParseDriver pd =new ParseDriver(); String sql="select a,b,c from tab where age =222"; ASTNode tree=null; try { tree=pd.parse(sql); } catch (ParseException e) { e.printStackTrace(); } System.out.println("AstNode:"+tree.dump()); } }
AstTree信息為:
【參考資料】
https://www.cnblogs.com/bonelee/p/12441814.html Hive架構和工作原理
https://blog.csdn.net/oTengYue/article/details/91129850 一文弄懂Hive基本架構和原理
https://www.cnblogs.com/lyr999736/p/9467854.html Hive的架構和工作流程
https://blog.csdn.net/wzq6578702/article/details/71250081 hive原理與源碼分析-hive源碼架構與理論(一)
https://zhuanlan.zhihu.com/p/273263917 hive源碼解讀(3)-文件介紹