从客户端提交一个 Hive SQL 到 Driver 提交 MapReduce Job,有一个对SQL进行词法分析和语义分析的过程,下面以 select count(*) from tableName 来描述其过程。
一、词法分析
使用ANTLR分析SQL,生成语法树,每个节点是一个 ASTNode,它有自己的类型。
来看看 select count(*) from tableName 的语法树:
(
TOK_QUERY
(
TOK_FROM
(
TOK_TABREF
(
TOK_TABNAME
tt2
)
)
)
(
TOK_INSERT
(
TOK_DESTINATION
(
TOK_DIR
TOK_TMP_FILE
)
)
(
TOK_SELECT
(
TOK_SELEXPR
(
TOK_FUNCTIONSTAR
count
)
)
)
)
)
上面语法树中,除括号外,每行对应一个 ASTNode,共13个节点,相互构成父子关系。
如:TOK_QUERY和TOK_INSERT为TOK_FROM的子节点;tt2为TOK_TABNAME的子节点。
二、语义分析
首先根据SQL类型创建对应的SemanticAnalyzer,对于这个sql,创建的就是 SemanticAnalyzer
1.分析语法树的每个节点,将后面要用到的相关数据保存到 Query Block
2.查询表的元数据信息,顺便做些检查,如检查 table 是否 offline
3.创建 Operator
4.优化
5.生成 Task
这些步骤中,除去辅助工作外,最重要的是3、5部分。
3 根据语法树创建Operator
生成的 Operator 是个 DAG,每个节点是一个 Operator,每个 Operator 应该有指向所有子 Operator的指针,那么Operator的数据结构至少是这个样子:
class Operator<T extends OperatorDesc> implements Node {
public List<Operator<? extends OperatorDesc>> getParentOperators() {
return parentOperators;
}
public List<Operator<? extends OperatorDesc>> getChildOperators() {
return childOperators;
}
}
先看看 select count(*) from tableName 的Operator:
为了生成 operator tree,首先创建 operator tree 的 root operator,而root operator 总是 TableScanOperator
这个步骤会为所有from字句中的表创建一个 TableScanOperator,将 TableScanOperator 和表名的对应关系保存起来。
最先创建的总是 TableScanOperator,以后每创建一个Operator 都将当前Operator作为它的父Operator
ReduceSinkOperator和它的子Operator: GroupByOperator是同时创建的,它来帮助mapper输出结果,接着就由Reduce进行聚集操作。
所以创建Task时,如遇到处理的节点为ReduceSinkOperator,就直接根据它的子Operator 来生成Reduce Task.
可见这个SQL 中 TableScanOperator 的子Operator 只有一个 SelectOperator;FileSinkOperator 有一个父Operator: SelectOperator
在explain 中就可查看有哪些Operator:
STAGE DEPENDENCIES:
Stage-1 is a root stage
Stage-0 is a root stage
STAGE PLANS:
Stage: Stage-1
Map Reduce
Alias -> Map Operator Tree:
tt2
TableScan //[1]
alias: tt2
Select Operator //[2]
Group By Operator //[3]
aggregations:
expr: count()
bucketGroup: false
mode: hash
outputColumnNames: _col0
Reduce Output Operator //[4]
sort order:
tag: -1
value expressions:
expr: _col0
type: bigint
Reduce Operator Tree:
Group By Operator //[5]
aggregations:
expr: count(VALUE._col0)
bucketGroup: false
mode: mergepartial
outputColumnNames: _col0
Select Operator //[6]
expressions:
expr: _col0
type: bigint
outputColumnNames: _col0
File Output Operator //[7]
compressed: false
GlobalTableId: 0
table:
input format: org.apache.hadoop.mapred.TextInputFormat
output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
Stage: Stage-0
Fetch Operator
limit: -1
上面 explain 中的Operator 已标出。
TableScanOperator 不用说了,望文生义吧
SelectOperator 可进行列修剪,可减少 mapper 的输出数据量。
GroupByOperator 进行聚集操作,map 端的自然只能做部分聚集了。
FileSinkOperator 向文件系统写SQL结果文件。
5根据 Operator tree 创建 Task
首先创建一个Dispatcher,它保存了用RegExp标识的规则和相应的节点处理器NodeProcessor的映射集合,
这个集合决定了每一个节点 Operator的命运,它决定了处理它的NodeProcessor.
不同的规则的正则表达式可匹配对应的 Operator。
然后顶端( TableScanOperator)开始遍历这个DAG,为每个节点(Operator)找出一个规则 Rule。
TableScanOperator 最匹配的规则对应的 NodeProcessor 是 GenMRTableScan1,它会创建一个 MapRedTask
SelectOperator 找到的 Processor 为 GenMROperator,这个 Processor 仅仅保存了一下SelectOperator 和对应的 GenMRProcContext$GenMapRedCtx 的映射关系
ReduceSinkOperator 对应的 NodeProcessor: GenMRRedSink1
上面说过,以创建Task时,如遇到处理的节点为ReduceSinkOperator,就直接根据它的子Operator 来生成Reduce Task,这就是 GenMRRedSink1 来完成的。
具体来说,GenMRRedSink1 将这个 GroupByOperator 作为 reducer,把它存入当前任务中。当前任务就是刚才处理TableScanOperator时创建的 Task。
所有节点处理完后会生成所有的Task,此时 PhysicalOptimizer 会再优化一次。它遍历的其实还是个DAG,不过这时节点是 Task了。
至此,Task已经创建,可以着手创建Mapeduce Job 了。