三、 Hive 逻辑算子及其生成(下)
来源:互联网 发布:php网站流量统计系统 编辑:程序博客网 时间:2024/06/01 10:43
3.3算子DAG的生成
在第一章1.4节我们提到Hive首先将抽象语法树AST转换成查询块(QueryBlock),然后在将QueryBlock转化成算子DAG。
3.3.1QueryBlock生成
1) QB数据结构
QueryBlock数据结构定义在QB类中,QB的主要成员变量如下:
HashMap<String, String> aliasToTabs;HashMap<String, QBExpr> aliasToSubq;HashMap<String, Map<String, String>> aliasToProps;List<String> aliases; QBParseInfo qbp; QBMetaData qbm; QBJoinTree qbjoin;
aliasToTabs: 表别名和表名映射关系。
aliasToSubq:子查询语句别名和子查询语句映射关系。其中QBExpr表示子查询语句的集合运算,Hive中定义了以下几种运算类型:
(1) NULLOP:表示单个子查询语句,没有任何运算。其QBExpr#qb字段表示子查询语句的QB。
(2) UNION:表示union运算,包含两个QBExpr对象,分别表示参与运算的两个子查询语句。
(3) INTERSECT:表示表的交集运算,目前尚未实现。
(4) EXCEPT:表示表的差集运算,目前尚未实现。
aliasToProps:表别名和表属性映射关系。
aliases:语句中所有表和子语句的别名。
qbp:QBParseInfo类型,记录该语句相关的信息。主要包括该语句是否为子查询语句、表别名、join语句语法树、where语句语法树、select语句语法树以及body语句的语法树(groupby、where,limit,clusterby,order by等语句)。insert语句还包含insert 目的数据源表名称列表。注意:由于HiveQL insert语句中可以包含多个insert分支(参考第二章insert语法树部分的介绍),每个分支包含select语句以及body语句,因此QBParseInfo使用hash表存放每个分支的select语句语法树以及body语句语法树。其中,hash表的key是分支的名称(Hive称之为dest,即insert的目的地名称),value为语法树。
qbm:QBMetaData类型,存放语句中表和分区的元数据信息。
qbjoin:QBJoinTree类型,记录Join树结构信息。第二章介绍过,join树是一颗左型树,QBJoinTree与之对应也是一棵左型树。关于QBJoinTree的具体介绍,参见下文。
2)QB的生成
Hive通过遍历抽象语法树(AST)生成QB对象,这一部分的核心代码位于SemanticAnalyzer#doPhase1方法中,伪代码如下:
doPhase1(ASTNode ast,dest,QB qb){QBParseInfo qbb=qb.getParseInfo()//存放语句相关信息Boolean recursion=false;//ast节点是否需要递归处理switch(ast.nodeType) {case TOK_DESTINATION: dest=newInsertName(); //生成新分支名称 qpb.setDest(dest,ast) //保存当前insert分支的目标数据源case TOK_SELECT: //保存当前insert分支的select表达式 qpb.setSelExpr(dest,ast) //保存当前insert分支的聚合函数表达式 qpb.setAggregationExpr(dest,getAggFunc(ast))case TOK_WHERE: //保存当前insert分支的where表达式 qbp.setWhereExpr(dest,ast)case TOK_GROUPBY: //保存当前insert分支的group by表达式 qbp.setGroupByExpr(dest,ast)case TOK_LIMIT: //保存目标insert分支的limit语句 qbp.setDestLimit(dest,ast)case 其他body语句节点(sortby,orderby等): 保存相应内容到当前的insert分支下,这里省略。case TOK_FROM: fromSrc=ast.getChild(0) if(fromSrc==TOK_TABREF) { processTable(qb,fromSrc) } else if(fromSrc==TOK_SUBQUERY) { processSubQuery(qb,fromSrc) } else if(isJoin(fromSrc) { processJoin(qb,frm) qb.setJoinExpr(fromSrc)//保存join AST树 } else 其他fromSrc …case 其他节点: //其他节点递归处理 recursion=true } if(recursion) for(child:ast.getChildren()) { doPhase1(child,dest,qb) }}
doPhase1方法根据ast节点的类型提取相应节点的信息,保存到qb对象中。如对于TOK_SELECT节点,保存select表达式以及聚合函数信息。遇到TOK_DESTINATION节点时,生成新分支名称,同时保存目标数据源信息(目标数据源表名称,临时文件路径等)。后续TOK_SELECT节点和body语句节点(TOK_GROUPBY,TOK_LIMIT等)的信息保存在该Insert分支下面。
对于TOK_FROM节点的处理比较复杂。第二章讲过,from的数据源包含多种类型, doPhase1对不同类型的数据来源处理方式不同:
(1) from TOK_TABREF
对于表数据源,通过processTable方法进行处理。processTable方法从语法树中提取表名称和别名等内容保存在QB对象相关数据结构中。
(2) from TOK_SUBQUERY
对于子查询语句,通过processSubQuery方法进行处理。processSubQuery方法主要代码如下:
processSubQuery(QB qb,ASTNode node) {ASTNode subq=node.getChild(0)String alias=node.getChild(1) QBExpr qbexpr=new QBExpr(alias)doPhase1QBExpr(subq,qbexpr) qb.setSubqAlias(alias,qbexpr) qb.addAlias(alias)}
processSubQuery方法调用doPhase1QBExpr方法生成subquery的QBExpr对象,然后存放到qb对象中。doPhase1QBExpr方法伪代码如下:
doPhase1QBExpr(ASTNode subq,QBExpr qbexpr) {switch(subq.nodeType) {case TOK_QUERY: QB qb=new QB()//构造子查询的QB对象 doPhase1(subq,qb,init_dest)//解析QB信息 qb.setOpcode(NULLOP) qbexpr.setQB(qb)case TOK_UNIONALL: QBExpr leftExpr=new QBExpr() QBExpr rightExpr=new QBExpr() //解析union左查询子语句QB doPhase1QBExpr(subq.getChild(0),leftExpr) //解析union右查询子语句QB doPhase1QBExpr(subq.getChild(1),rightExpr) qbexpr.setQBExpr1(leftExpr) qbexpr.setQBExpr2(rightExr) }}
(3)from JOIN
对于JOIN类型的数据源,通过processJoin方法进行处理。processJoin伪代码如下:
processJoin(QB qb,ASTNode join) {for(child:join.getChildren()) {//遍历join的所有子节点 if(child.nodeType==TOK_TABREF) { processTable(qb,child) } else if(child.nodeType==TOK_SUBQUERY) { processSubQuery(qb,child) else if(isJoin(child.nodeType) { processJoin(qb,child) } }}
processJoin遍历join语法树的所有数据源,调用数据源对于的方法进行处理。实际上,processJoin只是将join的所有数据源存放到QB对象中,在算子生成阶段再做进一步处理。
3)元数据信息处理
Hive生成QB对象后,接着调用getMetaData方法处理QB中所有表的元数据信息。getMetaData方法主要完成以下功能:
(1) 将QB中的对with语句的引用以替换成with语句的AST树,with语句语法树作为QB的subquery。
(2) 检查表是否offline,如果表已经下线,则认为查询非法,抛出异常。
(3) 将QB中对视图的引用替换成视图对应的语法树,视图作为QB的subquery。
(4)将每张表的元数据信息存放在QB#qbm中,表的元数据信息由org.apache.hadoop.hive.ql.metadata.Table对象表示。通过Table对象可以获取表的名称、数据库名称、InputFormat class、OutputFormat class以及字段信息内容等。
(5)处理子查询的元数据信息。通过递归调用getMetaData()进行处理。
(6) 处理查询语句的目的数据源。目的数据源存放查询语句的结果,包括表和文件目录两种。getMetaData提取目的数据源的元数据信息以及文件目录信息到QB#qbm字段中。
3.3.2 算子DAG的生成
Hive遍历抽象语法树(AST)生成QB对象,然后将QB对象生成算子DAG。算子DAG生成的代码在SemanticAnalyzer#genPlan(QB)方法中,主要流程如下图所示:
图3-9 算子DAG生成流程图
genPlan(QB)方法依次生成流程图中个模块的算子DAG,然后拼接得到整个语句的算子DAG。
3.3.2.1 生成子查询算子DAG
genPlan对QB中所有子查询QBExpr调用genPlan(QB,QBExpr)方法生成对应的算子DAG。genPlan(QB,QBExpr)伪代码实现如下:
genPlan(QB parent,QBExpr qbexpr) { if(qbexpr.getOpcode()==NULLOP) //单一查询语句 return genPlan(qbexpr.getQB()) else if(qbexpr.getOpcode()==UNION) {//UNION查询语句 //生成UNION左查询语句DAG opLeft=genPlan(parent,qbexpr.getQBExpr1()) //生成UNION右边查询语句DAG opRight=genPlan(parent,qbexpr.getQBExpr2()) return genUnionPlan(opLeft,opRight) } return null;//其他集合运算如except等Hive不支持}
这里genPlan方法的返回值是算子DAG最后一个节点。gentPlan(QB,QBExpr)调用genUnionPlan(opLeft,opRight)方法生成union语句的算子DAG。opLeft和opRight分别是union语句左右查询语句算子树的最后一个节点。如果opLeft和opRight都是单一查询语句(即QBExpr#getOpcode()为NULLOP),那么gentUnionPlan生成一个UNION算子,opLeft和opRight算子作为UNION算子的父亲点,如下图所示。
图3-10 union算子DAG
第二章语法树部分介绍过,union的语法树是一颗左型树,即左子节可以嵌套包含一个union节点,因此,opLeft算子树可以嵌套包含一棵union算子树。genUnionPlan方法会将rightOp作为opLeft union算子树的子节点合并到opLeft union算子树中去,如下图所示:
图3-11 union算子分支合并
3.3.2.2 生成源表的算子DAG
这一步对QB中的所有源表生成TS算子,例如select* from a join b,会分别给源表a和源表b生成TS算子。同时会将TS算子存放到topOps列表中,供后面使用,具体实现参考genTablePlan方法。
步骤1)和步骤2)生成的都是From数据源的算子DAG,这些DAG保存在Map<String,Operator> aliasToOpInfo中,后面步骤会用到。
PTF函数和LateralView的算子DAG生成本文暂不做介绍。
3.3.2.3生成Join算子DAG
Hive生成Join算子DAG的过程分两个阶段:生成QBJoinTree和生成Join算子DAG。
1) 生成QBJoinTree
Hive使用QBJoinTree对象描述Join的数据结构。第二章介绍过,Join语法树是一棵左型树,因此QBJoinTree对象与之对应也是一棵左型树。QBJoinTree的主要数据结构如下:
String leftAlias;String[] rightAliases;String[] leftAliases;QBJoinTree joinSrc;String[] baseSrc;JoinCond[] joinCond;ArrayList<ArrayList<ASTNode>> expressions;ArrayList<ArrayList<ASTNode>> filters
其中:
leftAlias:当Join Tree的左节点是叶子节点时(即不嵌套包含另外一棵Join Tree),leftAlias为join左数据源(可以是表,subquery等)的别名;否则,该字段为null。
rightAliases:Join Tree右数据源的别名。
leftAliases:JoinTree中除了最后一个数据源节点(在Join Tree的最右边)以外的所有数据源的别名。
joinSrc:当Join Tree的左节点为非叶子节点(即嵌套包含一棵Join Tree)时,该字段为左节点对应的QBTree对象;否则该字段为null。
baseSrc:Join Tree左右节点的数据源别名,为长度为2的数组。如果左节点为非叶子节点,那么baseSrc[0]为null。
joinCond:表示Join Tree的join类型。join类型包括left outer join、right outer join、full outer join、left semijoin以及inner join。
expressions:join on链接表达式。如joinon a.id=b.id。
filters:join on过滤表达式。如joinon a.id>100。
Hive调用SemanticAnalyzer#genJoinTree方法深度优先遍历Join语法树,生成对应的QBJoinTree对象,具体细节请参考这一部分的代码。
2) Join 算子DAG的生成
这一步调用genJoinOperator方法生成Join算子DAG。genJoinOperator实现的伪代码如下:
genJoinOperator(QB qb,QBJoinTree joinTree) {QBJoinTree leftChild=joinTree.joinSrc //join算子的左右父分支 Operator joinLeft,joinRight; if(leftChild==null) { joinLeft = aliasToOpInfo.get(joinTree.baseSrc[0]) }else { //生成左节点算子DAG joinLeft =genJoinOperator(qb,leftChild) joinRight =aliasToOpInfo.get(joinTree.baseSrc[1]) joinLeft =genNotNullFilter(qb,joinLeft,joinTree) joinLeft =genReduceSinkOperator(qb,joinLeft,joinTree) joinRight = genNotNullFilter(qb,joinRight,joinTree) joinRight =genReduceSinkOperator(qb,joinRight,joinTree) return genJoinOp(joinTree, joinLeft, joinRight)}
step1:取QBJoinTree的左子树(即joinSrc字段),如果为空,表明左节点是非叶子节点,执行step 2;否则,左节点是一棵QBTree, 执行setp3。
step2:从aliasToOpInfo中获取左节点数据源对应的算子DAG存放到joinLeft变量。如果左节点数据源是表,那么joinLeft是该表数据源的算子DAG;如果该数据源是subquery,那么joinLeft是该subquery生成的DAG;其他数据源类似。执行setp4。
step3:递归调用genJoinOperator生成该子QBTree的算子DAG,存放到joinLeft中。执行setp4。
step4: 从aliasToOpInfo中获取右节点数据源对应的算子DAG存放到joinRight变量。执行step5。
step5:首先,给joinLeft算子添加FilterOperater过滤左分支join key为null的数据。然后再添加RS算子。对joinRight执行同样的操作。执行step6。
step6:生成Join算子,并将joinLeft和joinRight作为Join算子的父节点。
以下面例子分析join算子DAG的生成过程:
select * from a join b on a.id=b.idHiveQL语句对应QBJoinTree示意图如图3-11。该语句的QBJoinTree包含两个叶子节点:table a和table b。Join算子DAG的生成流程如下:
图3-12 QBJoinTree
图3-13 join算子DAG生成
首先执行step1、step2,从aliasToOpInfo中获取table a和table b的TS算子;由于然后执行step4,添加FIL算子,过滤a.id为null以及b.id为null的记录;接着添加RS算子,RS算子按照id进行数据分发以及排序;最后执行step6,生成Join算子,joinLeft和joinRight作为其父节点,完成join算子树的生成。
3.3.2.4生成Body语句算子DAG
这一阶段生成select语句和body语句的算子DAG,分三个阶段:where语句算子DAG生成、groupby语句算子DAG生成以及 group by之外其他语句算子DAG生成。Hive中可以包含多个insert分支,每个分支都包含select语句和body语句,Hive分别给每个insert分支生成算子DAG。
1) where语句的算子DAG生成
Hive将where语句生成FIL算子,过滤数据源中不符合where表达式的记录。将FIL算子的生成放在第一个,是为了提前进行数据过滤,减少后续数据处理量。生成FIL算子的代码位于SemanticAnalyzer#genFilterPlan方法中。
2) group by语句算子DAG生成
Hive中提供了group by优化的两个重要配置hive.map.aggr和hive.groupby.skewindata。其中hive.map.aggr(默认true)控制是否需要map端group by聚合,即hashgroup by。hive.groupby.skewindata(默认false)用于优化数据倾斜导致的性能问题。如果该参数为true,在生成group by算子DAG时,会生成两个阶段group by过程:第一阶段group by执行局部聚合并对数据进行均匀分发,保证数据分布的均衡性;第二阶段执行最终的group by操作。
group by算子DAG根据这两个参数配置的不同,生成的DAG分以下四种情况:
(1) hive.map.aggr=true && hive.groupby.skewindata=true
stage1为第一阶段的group by运算,主要完成数据的均匀分发。第一个GBY算子为hash based GBY,实现局部聚合,减少数据的输出量。紧接着,RS算子进行数据分发和排序。数据分发和排序的规则如下:
a) 如果聚合函数中不包含distinct,那么数据随机分发。
b) 如果聚合函数中包含distinct,那么数据按照group by key+distinct key分发。
第二个GBY算子为sort base GBY,对数据进行局部聚合。然后进入stage2,通过RS算子按照groupby key进行数据分发和排序。 最后执行GBY算子,执行合并操作,得到最终结果。
(2) hive.map.aggr=true && hive.groupby.skewindata=false
这种情况下只有state1的算子,DAG如下图所示。其中hash based GBY算子执行map端局部聚合运算,RS算子对数据进行分发和排序,分发和排序的规则为:根据group by key进行数据分发,group by key+distinct key进行排序。最后执行sort based GBY进行数据合并运算。
(3)hive.map.aggr=false && hive.groupby.skewindata=true
和(1)相比,减少了map端的hashbased GBY,其他情况和(1)相同。
(4)hive.map.aggr=false && hive.groupby.skewindata=false
和(2)相比,减少了map端的hash based GBY,其他情况和(2)相同。
3)group by之外其他语句算子DAG生成
这一阶段完成对select、having、sortby、order by、distribute by、limit以及cluster by语句的算子DAG的生成。代码位于SemanticAnalyzer#genPostGroupByBodyPlan方法中,流程图如下:
图3-14 group by之外语句的算子DAG生成
首先处理Having语句,如果group by后面有having语句,则调用genHavingPlan方法,给having语句生成FIL算子。
其次生成select算子,select算子的生成代码位于genSelectPlan方法中。
然后,判断语句中是否存在sort by、order by、distriubteby和cluster by这些与数据排序分区相关的语句。如果存在,则生成相应的RS算子,RS算子生成的代码位于genReduceSinkPlan中。
接着,生成Limit算子,如果存在limit语句,则生成相应的LIM算子。LIM算子生成的代码位于genLimitMapRedPlan方法中。
最后,判断是否为subquery子查询语句,如果是则结束流程;否则生成FS算子,输出数据。
- 三、 Hive 逻辑算子及其生成(下)
- 三、 Hive 逻辑算子及其生成(上)
- 三、 Hive 逻辑算子及其生成(中)
- 逻辑非算子
- HIVE下分布式生成整型唯一ID
- Spark算子(三)
- 四 、Hive逻辑优化
- Hive解析HiveQL语句生成抽象语法树和逻辑计划
- 逻辑回归及其分析
- 三种边缘检测算子
- Spark RDD算子【三】combineByKey
- Hive介绍及其架构
- Hive及其架构
- 常用边缘检测算子及其特性
- Halcon中count_obj算子及其异常分析
- 【学习opencv】Sobel算子原理及其实现
- linux下hive三种方式的安装
- Python 下的 lambda 算子
- Http协议学习笔记
- 基于HttpClient的HttpUtils(后台访问URL)
- 使用Mockplus的九大理由
- 这是一个传奇的算法(平方根倒数速算法)
- cesium js学习一加载三维模型
- 三、 Hive 逻辑算子及其生成(下)
- leetcode:Palindrome Partitioning
- FastJson构造json串空的问题
- Window环境中开发Android之adb连接手机问题解决
- linux中如何快速在某目录下打开终端 及vim多文件调试
- uboot CMD 命令体系
- hdu2571 命运--DP
- leetcode ZigZag Conversion 006
- 排序——希尔排序(C++)