Hive SQL编译原理(下)
三、过渡
1 Driver.compile()
2 SemanticAnalyzerFactory
是Hive做语义解析的工厂类,用于根据SQL的类型,构建对应的语义解析器。
比如:explain走ExplainSemanticAnalyzer,DDL走DDLSemanticAnalyzer,load走LoadSemanticAnalyzer等等,工厂模式可以使这些不同的功能隔离开,在一定程度上解耦,也增加了可扩展性,比如某天需要再添加个import数据的编译过程,开发个ImportSemanticAnalyzer类 在SemanticAnalyzerFactory工厂里注册一下就ok了。
一般执行查询时,都是进入default,构建SemanticAnalyzer或者CalcitePlanner(开启CBO)解析器
3 BaseSemanticAnalyzer
- 抽象基类,不可以被实例化。
- 它定义了语义分析器的入口,即analyze(ASTNode, Context),该入口做了:
- 把Context保存在成员变量ctx中;
- 调用 analyzeInternal(ASTNode)完成分析工作,这个函数是抽象函数,应该被各个子类分别override,以完成相应的分析逻辑。
4 Hive的语义分析器
语义分析主要通过 BaseSemanticAnalyzer 实现类中的 analyze 方法进行。不同的sql语句会用不同的 BaseSemanticAnalyzer实现类来进行分析,主要有以下语义分析器:
5 SemanticAnalyzer中的analyze方法
未开启CBO的情况下,查询语句的语义解析都是调用SemanticAnalyzer.analyze的,anlyze最终转向的是各个语义分析器中的 analyzeInternal 方法,SemanticAnalyzer中的analyzeInternal 代码如下:
从代码看,整个过程主要是以下的步骤:
- 从抽象语法树生成 resolved parse tree (AST->QB)
- 从 resolved parse tree 生成 op tree
- 推断结果集表结构
- 为优化器和物理编译器生成上下文
- 执行逻辑优化
- 优化物理执行树 & 翻译成目标执行引擎
四、阶段2-语义解析
1 语义解析总过程
经历了词法解析和语法解析后,SQL被编译成了ASTTree,AST Tree仍然非常复杂,不够结构化,不方便直接翻译为MapReduce程序,同时ASTTree缺少很多信息,比如涉及的表是外部表还是内部表,字段类型是什么,存储位置在哪,AST Tree转化为QueryBlock就是将SQL进一部抽象和结构化。
语义解析就是从ASTTree生成QueryBlock的过程,即从抽象语法树中找出所有的基本单元以及每个单元之间的关系的过程。每个基本单元创建一个QB对象,将每个基本单元的不同操作转化为QB对象的不同属性。
2 源码分析
2.1 SemanticAnalyzer中的genResolvedParseTree方法
2.2 doPhase1
doPhase1主要是递归地遍历AST,做基本的语义检查,并建立下面的映射关系表:
① 找到所有表、子查询的别名,并设置在aliasToTable、aliasToSubq表中;
② 找到所有内部clause的结果的目标及其名字;
③ 建立从所有聚合操作的AST的字符串表示到其AST本身的映射关系;
④ 建立clause名字到其select表达式AST的映射关系;
⑤ 建立alias到lateral view的映射关系;
上面所有这些映射关系都保存在QB/QBParseInfo中。
参数qb是一个空的QB,在不同case类型下对齐进行填满。doPhase1对ASTTree中的每个元素的TOK类型进行case,针对于不同的case对节点数据进行填充。
for遍历整棵ASTTree,中间对每个元素递归调用doPhase1,这种方式是一种深度优先搜索的算法。经过一轮深度优先遍历,不带元数据的QB树就生成了。
2.3 getMetaData
doPhase1执行完毕之后得到QB,QB里边的只是一些关键字还有一些表的名字,但是和hdfs的文件路径对应不起来,所以需要metaData映射关系,所以SemanticAnalyzer中调用了getMetaData()。
该方法获取源表、目标表的元数据(主要是schema等信息),获取的元数据同样存储在QB/QBParseInfo中。
- 获取source table的元数据,如果一个table实际上是一个view,将其重写为view的定义;
- 递归的为每个子查询中的源表获得元数据;
- 获取所有destination table/dir/local dir的元数据;
当这一切都执行完了之后,语义解析模块就结束了。
四、生成逻辑执行计划
生成逻辑计划(Logical Plan Gen)
Hive最终生成的MapReduce任务,Map阶段和Reduce阶段均由OperatorTree组成。逻辑操作符,就是在Map阶段或者Reduce阶段完成单一特定的操作。
上一步语义解析,对应的是SemanticAnalyzer的analyzeInternal方法中的genResolvedParseTree(ast, plannerCtx),详见2.5节,那么本章节生成逻辑执行计划就是该方法的genResolvedParseTree的下一个步骤,从 resolved parse tree 生成 op tree
Operator sinkOp = genOPTree(ast, plannerCtx);
关键方法:genPlan
基本的操作符如下所示:
通过QueryBlock可以生成Operator Tree就是遍历上一个过程中生成的QB和QBParseInfo对象的保存语法的属性,包含如下几个步骤:
(1) QB#aliasToSubq => 有子查询,递归调用
(2) QB#aliasToTabs => TableScanOperator
(3) QBParseInfo#joinExpr => QBJoinTree => ReduceSinkOperator + JoinOperator
(4) QBParseInfo#destToWhereExpr => FilterOperator
(5) QBParseInfo#destToGroupby => ReduceSinkOperator + GroupByOperator
(6) QBParseInfo#destToOrderby => ReduceSinkOperator + ExtractOperator
五、优化逻辑计划(Logical Optinizer)
大部分逻辑层优化器通过变换Operator Tree,合并操作符,达到减少MapReduce Job,减少shuffle数据量的目的。
六、生成物理计划(Physical Plan Gen)
在生成相应的查询计划之后,hive需要将逻辑计划转换成一个物理查询计划,这里是将其转换成MapReduce作业
七、物理任务优化(Physical Optimizer)
根据sql语句的不同,map和reduce中包含的操作符也各不相同,但是某些操作符可以进行压缩,合并成一个操作符。接着则启动相应的MapTask和ReduceTask。
八、任务执行计划
SemanticAnalyzer.analyzeInternal的第9步
1、构造对应的引擎编译器
TaskCompiler compiler = TaskCompilerFactory.getCompiler(conf, pCtx);
TaskCompilerFactory是工厂类,TaskCompilerFactory. getCompiler是根据hive.execution.engine配置来构造对应的引擎编译器
2、引擎编译器初始化
compiler.init(queryState, console, db);
其中TezCompiler和MapReduceCompiler重写了TaskCompiler的init方法,主要是开启相关参数
3、执行编译
compiler.compile(pCtx, rootTasks, inputs, outputs);
4、fetchTask = pCtx.getFetchTask();
- 点赞
- 收藏
- 关注作者
评论(0)