無論你通過哪種方式連接Hive(如Hive Cli、HiveServer2),一個HQL語句都要經過Driver的解析和執行,主要涉及HQL解析、編譯、優化器處理、執行器執行四個方面。
以Hive目前原生支持計算引擎MapReduce為例,具體處理流程如下:
- HQL解析生成AST語法樹Antlr定義SQL的語法規則,完成SQL詞法和語法解析,將SQL轉化為抽象語法樹AST Tree
- 語法分析得到QueryBlock遍歷AST Tree,抽象出查詢的基本組成單元QueryBlock
- 生成邏輯執行計划遍歷QueryBlock,翻譯為執行操作樹Operator Tree
- Logical Optimizer Operator進行邏輯優化邏輯層優化器進行OperatorTree變換,合並不必要的ReduceSinkOperator,減少shuffle數據量
- 生成物理執行計划Task Plan遍歷Operator Tree,翻譯為MapReduce任務
- 物理優化Task Tree,構建執行計划QueryPlan物理層優化器進行MapReduce任務的變換,生成最終的執行計划
- 表以及其他操作鑒權
- 執行引擎執行
在Hive Query整個生命周期中,會有如下鈎子函數被執行:
HiveDriverRunHook的preDriverRun
該鈎子函數由參數hive.exec.driver.run.hooks控制,決定要運行的pre hooks,多個鈎子實現類以逗號間隔,鈎子需實現 org.apache.hadoop.hive.ql.HiveDriverRunHook接口。
HiveSemanticAnalyzerHook的preAnalyze
在Driver開始run之前,HQL經過解析會進入編譯階段的語法分析,而在語法分析前會經過鈎子HiveSemanticAnalyzerHook的preAnalyze方法處理。該鈎子函數由hive.semantic.analyzer.hook配置,鈎子需實現org.apache.hadoop.hive.ql.parse.HiveSemanticAnalyzerHook接口。
HiveSemanticAnalyzerHook的postAnalyze
與preAnalyze同屬於一個鈎子類,配置參數相同,會執行所有配置的語義分析hooks,但它位於Hive的語法分析之后,可以獲取HQL的輸入和輸出表及分區信息,以及語法分析得到的task信息,由此可以判斷是否是需要分布式執行的任務,以及執行引擎是什么。
生成執行計划之前的redactor鈎子
該鈎子由hive.exec.query.redactor.hooks配置,多個實現類以逗號間隔,鈎子需繼承org.apache.hadoop.hive.ql.hooks.Redactor抽象類,並替換redactQuery方法。
這個鈎子函數是在語法分析之后,生成QueryPlan之前,所以執行它的時候語法分析已完成,具體要跑的任務已定,這個鈎子的目的在於完成QueryString的替換,比如QueryString中包含敏感的表或字段信息,在這里都可以完成替換,從而在Yarn的RM界面或其他方式查詢該任務的時候,會顯示經過替換后的HQL。
task執行前的preExecutionHook
在執行計划QueryPlan生成完,並通過鑒權后,就會執行具體的task,而task執行之前會經過一個鈎子函數,鈎子函數由hive.exec.pre.hooks配置,多個鈎子實現類以逗號間隔。實現方式:
1)實現org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext
通過實現該接口的run方法,執行所有的pre-execution hooks
// Pre/Post Execute Hook can run with the HookContext public interface ExecuteWithHookContext extends Hook { /** hookContext: The hook context passed to each hooks. * HookContext帶有執行計划、Hive的配置信息、Lineage、UGI、提交的用戶以及輸入輸出表等信息 */ void run(HookContext hookContext) throws Exception; }
2)實現org.apache.hadoop.hive.ql.hooks.PreExecute
該接口的run方法已經標注為過時,並且相對於ExecuteWithHookContext,PreExecute提供的信息可能不能完全滿足我們的業務需求。
public interface PreExecute extends Hook { /** * The run command that is called just before the execution of the query. * SessionState、UGI、HQL輸入表及分區信息,HQL輸出表、分區以及本地和hdfs文件目錄信息 */ @Deprecated public void run(SessionState sess, Set<ReadEntity> inputs,Set<WriteEntity> outputs, UserGroupInformation ugi) throws Exception; }
task執行失敗時的ON_FAILURE_HOOKS
task執行失敗時,Hive會調用這個hook執行一些處理措施。該鈎子由參數hive.exec.failure.hooks配置,多個鈎子實現類以逗號間隔。需實實現org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext接口。
task執行完成時的postExecutionHook
在task任務執行完成后執行。如果task失敗,會先執行ON_FAILURE_HOOKS,之后執行postExecutionHook,該鈎子由參數hive.exec.post.hooks指定的hooks(多個鈎子實現類以逗號間隔)執行post execution hooks。實現方式:
1)實現org.apache.hadoop.hive.ql.hooks.ExecuteWithHookContext
2)實現org.apache.hadoop.hive.ql.hooks.PostExecute
ExecuteWithHookContext和PostExecute跟分別與上述task執行前的preExecutionHook、PreExecute對應,這里不再贅述。
HiveDriverRunHook的postDriverRun
在查詢完成運行之后以及將結果返回給客戶端之前執行,與preDriverRun對應。
此外,Hive中已經有一些內置實現的hook,下面舉一些例子以及它們的主要作用:
ATSHook:實現了ExecuteWithHookContext,將查詢和計划信息推送到Yarn App Timeline Server。
DriverTestHook:實現了HiveDriverRunHook的preDriverRun方法(對postDriverRun是空實現),用於打印輸出的命令。
EnforceReadOnlyTables:pre execute hook,實現了ExecuteWithHookContext,用於阻止修改只讀表。
LineageLogger:實現了ExecuteWithHookContext,它將查詢的血統信息記錄到日志文件中。LineageInfo包含有關query血統的所有信息。
PreExecutePrinter和PostExecutePrinter:pre和post hook的示例,它將參數打印輸出。
PostExecTezSummaryPrinter:post execution hook,實現了ExecuteWithHookContext,可以打印Hive Tez計數器的相關信息。
PostExecOrcFileDump:post execution hook,實現了ExecuteWithHookContext,用於打印ORC文件信息。
UpdateInputAccessTimeHook:pre execution hook,可在運行查詢之前更新所有輸入表的訪問時間。
特別強調一下LineageLogger和LineageInfo,對於做Hive血緣關系分析很有參考價值,當然Hive血緣分析不是本篇文章的重點,這里先不做展開。
通過對上面Hive中hook的執行"位置"和作用,以及Hive本身實現的一些Hook,分析可知:自定義hook,比如實現一個pre execution hook。
首先在maven的pom中引入hive-exec的依賴,如:
<dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-exec</artifactId> <version>2.1.0</version> </dependency>
此外,還需創建一個實現ExecuteWithHookContext的類,實現其中的run方法,並設置相應的參數,使自定義的hook類生效。
最后,通過一張圖,來對Hive Hook做個總結: