【HIVE】sql语句转换成mapreduce
1.hive是什么?
2.MapReduce框架实现SQL基本操作的原理是什么?
3.Hive怎样实现SQL的词法和语法解析?
连接:
美团⼤众点评上:
hive是什么?
Hive是基于Hadoop的⼀个数据仓库系统,在各⼤公司都有⼴泛的应⽤。美团数据仓库也是基于Hive搭建,每天执⾏近万次的Hive ETL计算流程,负责每天数百GB的数据存储和分析。Hive的稳定性和性能对我们的数据分析⾮常关键。
在⼏次升级Hive的过程中,我们遇到了⼀些⼤⼤⼩⼩的问题。通过向的咨询和⾃⼰的努⼒,在解决这些问题的同时我们对Hive将SQL编译为MapReduce的过程有了⽐较深⼊的理解。对这⼀过程的理解不仅帮助我
们解决了⼀些Hive的bug,也有利于我们优化Hive SQL,提升我们对Hive的掌控⼒,同时有能⼒去定制⼀些需要的功能。
MapReduce实现基本SQL操作的原理
详细讲解SQL编译为MapReduce之前,我们先来看看MapReduce框架实现SQL基本操作的原理
Join的实现原理
select u.name, o.orderid from order o join user u on o.uid = u.uid;
在map的输出value中为不同表的数据打上tag标记,在reduce阶段根据tag判断数据来源。MapReduce的过程如下(这⾥只是说明最基本的Join的实现,还有其他的实现⽅式)
Group By的实现原理
select rank, isonline, count(*) from city group by rank, isonline;
将GroupBy的字段组合为map的输出key值,利⽤MapReduce的排序,在reduce阶段保存LastKey区分不同的key。MapReduce的过程如下(当然这⾥只是说明Reduce端的⾮Hash聚合过程)
Distinct的实现原理
select dealid, count(distinct uid) num from order group by dealid;
当只有⼀个distinct字段时,如果不考虑Map阶段的Hash GroupBy,只需要将GroupBy字段和Distinct字段组合为map输出key,利⽤mapreduce的排序,同时将GroupBy字段作为reduce的key,在reduce阶段保存LastKey即可完成去重
如果有多个distinct字段呢,如下⾯的SQL
select dealid, count(distinct uid), count(distinct date) from order group by dealid;
实现⽅式有两种:
(1)如果仍然按照上⾯⼀个distinct字段的⽅法,即下图这种实现⽅式,⽆法跟据uid和date分别排序,也就⽆法通过LastKey去重,仍然需要在reduce阶段在内存中通过Hash去重
(2)第⼆种实现⽅式,可以对所有的distinct字段编号,每⾏数据⽣成n⾏数据,那么相同字段就会分别排序,这时只需要在reduce阶段记录LastKey即可去重。
这种实现⽅式很好的利⽤了MapReduce的排序,节省了reduce阶段去重的内存消耗,但是缺点是增加了shuffle的数据量。
需要注意的是,在⽣成reduce value时,除第⼀个distinct字段所在⾏需要保留value值,其余distinct数据⾏value字段均可为空。
SQL转化为MapReduce的过程
了解了MapReduce实现SQL基本操作之后,我们来看看Hive是如何将SQL转化为MapReduce任务的,整个编译过程分为六个阶段:Antlr定义SQL的语法规则,完成SQL词法,语法解析,将SQL转化为抽象语法树AST Tree
遍历AST Tree,抽象出查询的基本组成单元QueryBlock
遍历QueryBlock,翻译为执⾏操作树OperatorTree
逻辑层优化器进⾏OperatorTree变换,合并不必要的ReduceSinkOperator,减少shuffle数据量
遍历OperatorTree,翻译为MapReduce任务
物理层优化器进⾏MapReduce任务的变换,⽣成最终的执⾏计划hive 字符串转数组
下⾯分别对这六个阶段进⾏介绍
Phase1 SQL词法,语法解析
Antlr
Hive使⽤Antlr实现SQL的词法和语法解析。Antlr是⼀种语⾔识别的⼯具,可以⽤来构造领域语⾔。
这⾥不详细介绍Antlr,只需要了解使⽤Antlr构造特定的语⾔只需要编写⼀个语法⽂件,定义词法和语法替换规则即可,Antlr完成了词法分析、语法分析、语义分析、中间代码⽣成的过程。
Hive中语法规则的定义⽂件在0.10版本以前是Hive.g⼀个⽂件,随着语法规则越来越复杂,由语法规则⽣成的Java解析类可能超过Java类⽂件的最⼤上
限,0.11版本将Hive.g拆成了5个⽂件,词法规则HiveLexer.g和语法规则的4个⽂件
SelectClauseParser.g,FromClauseParser.g,IdentifiersParser.g,HiveParser.g。
抽象语法树AST Tree
经过词法和语法解析后,如果需要对表达式做进⼀步的处理,使⽤ Antlr 的抽象语法树语法Abstract Syn
tax Tree,在语法分析的同时将输⼊语句转换成抽象语法树,后续在遍历语法树时完成进⼀步的处理。
下⾯的⼀段语法是Hive SQL中SelectStatement的语法规则,从中可以看出,SelectStatement包含select, from, where, groupby, having, orderby等⼦句。
(在下⾯的语法规则中,箭头表⽰对于原语句的改写,改写后会加⼊⼀些特殊词标⽰特定语法,⽐如TOK_QUERY标⽰⼀个查询块)
[SQL] 纯⽂本查看复制代码
01
02 03 04 05 06 07 08 09 10 11selectStatement
:
selectClause
fromClause
whereClause?
groupByClause?
havingClause?
orderByClause?
clusterByClause? distributeByClause? sortByClause?
12 13 14 15 limitClause? -> ^(TOK_QUERY fromClause ^(TOK_INSERT ^(TOK_DESTINATION ^(TOK_DIR TOK_TMP_FILE)) selectClause whereClause? groupByClause? havingClause? orderByClause? clusterByClause?
distributeByClause? sortByClause? limitClause?))
;
样例SQL
为了详细说明SQL翻译为MapReduce的过程,这⾥以⼀条简单的SQL为例,SQL中包含⼀个⼦查询,最终将数据写⼊到⼀张表中[SQL] 纯⽂本查看复制代码
01
02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18FROM
(
SELECT
p.datekey datekey,
p.userid userid,
c.clienttype
FROM
detail.usersequence_client c
WHEREp.datekey = 20131118
)
base
INSERTOVERWRITE TABLE`test`.`customer_kpi` SELECT
base.datekey,
base.clienttype,
count(distinctbase.userid) buyer_count GROUPBY base.datekey, base.clienttype
SQL⽣成AST Tree
Antlr对Hive SQL解析的代码如下,HiveLexerX,HiveParser分别是Antlr对语法⽂件Hive.g编译后⾃动⽣成的词法解析和语法解析类,在这两个类中进⾏复杂的解析。
[SQL] 纯⽂本查看复制代码
01
02 03 04 05 06 07 08 09 10 11 12 13 14HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command)); //词法解析,忽略关键词的⼤⼩写TokenRewriteStream tok
ens = new TokenRewriteStream(lexer);
if (ctx != null) {
ctx.setTokenRewriteStream(tokens);
}
HiveParser parser = new HiveParser(tokens); //语法解析
parser.setTreeAdaptor(adaptor);
HiveParser.statement_return r = null;
try {
r = parser.statement(); //转化为AST Tree
} catch (RecognitionException e) {
e.printStackTrace();
throw new s);
}
最终⽣成的AST Tree如下图右侧(使⽤Antlr Works⽣成,Antlr Works是Antlr提供的编写语法⽂件的编辑器),图中只是展开了⾻架的⼏个节点,没有完全展开。
⼦查询1/2,分别对应右侧第1/2两个部分。
这⾥注意⼀下内层⼦查询也会⽣成⼀个TOK_DESTINATION节点。请看上⾯SelectStatement的语法规则,这个节点是在语法改写中特意增加了的⼀个节点。原因是Hive中所有查询的数据均会保存在HDFS临时的⽂件中,⽆论是中间的⼦查询还是查询最终的结果,Insert语句最终会将数据写⼊表所在的HDFS⽬录下。
详细来看,将内存⼦查询的from⼦句展开后,得到如下AST Tree,每个表⽣成⼀个TOK_TABREF节点,Join条件⽣成⼀个“=”节点。其他SQL部分类似,不⼀⼀详述。
Phase2 SQL基本组成单元QueryBlock
AST Tree仍然⾮常复杂,不够结构化,不⽅便直接翻译为MapReduce,AST Tree转化为QueryBlock就是将SQL进⼀部抽象和结构化。
QueryBlock
QueryBlock是⼀条SQL最基本的组成单元,包括三个部分:输⼊源,计算过程,输出。简单来讲⼀个QueryBlock就是⼀个⼦查询。
下图为Hive中QueryBlock相关对象的类图,解释图中⼏个重要的属性
QB#aliasToSubq(表⽰QB类的aliasToSubq属性)保存⼦查询的QB对象,aliasToSubq key值是⼦查询的别名
QB#qbp 即QBParseInfo保存⼀个基本SQL单元中的给个操作部分的AST Tree结构,QBParseInfo#nameToDest这个HashMap保存查询单元的输出,key的形式是inclause-i(由于Hive ⽀持Multi Insert语句,所以可能有多个输出),value是对应的ASTNode节点,即TOK_DESTINATION节点。类QBParseInfo其余 HashMap属性分别保存输出和各个操作的ASTNode节点的对应关系。
QBParseInfo#JoinExpr保存TOK_JOIN节点。QB#QBJoinTree是对Join语法树的结构化。
QB#qbm保存每个输⼊表的元信息,⽐如表在HDFS上的路径,保存表数据的⽂件格式等。
QBExpr这个对象是为了表⽰Union操作。
AST Tree⽣成QueryBlock
AST Tree⽣成QueryBlock的过程是⼀个递归的过程,先序遍历AST Tree,遇到不同的Token节点,保存到相应的属性中,主要包含以下⼏个过程TOK_QUERY => 创建QB对象,循环递归⼦节点
TOK_FROM => 将表名语法部分保存到QB对象的TOK_INSERT => 循环递归⼦节点
TOK_DESTINATION => 将输出⽬标的语法部分保存在QBParseInfo对象的nameToDest属性中
TOK_SELECT => 分别将查询表达式的语法部分保存在destToAggregationExprs、TOK_WHERE => 将Where部分的语法保存在QBParseInfo对象的destToWhereExpr属性中
最终样例SQL⽣成两个QB对象,QB对象的关系如下,QB1是外层查询,QB2是⼦查询
QB1 \ QB2
Phase3 逻辑操作符Operator
Operator
Hive最终⽣成的MapReduce任务,Map阶段和Reduce阶段均由OperatorTree组成。逻辑操作符,就是
在Map阶段或者Reduce阶段完成单⼀特定的操作。
基本的操作符包括TableScanOperator,SelectOperator,FilterOperator,JoinOperator,GroupByOperator,ReduceSinkOperator
从名字就能猜出各个操作符完成的功能,TableScanOperator从MapReduce框架的Map接⼝原始输⼊表的数据,控制扫描表的数据⾏数,标记是从原表中取数据。JoinOperator完成Join操作。FilterOperator完成过滤操作
ReduceSinkOperator将Map端的字段组合序列化为Reduce Key/value, Partition Key,只可能出现在Map阶段,同时也标志着Hive⽣成的MapReduce程序中Map阶段的结束。
Operator在Map Reduce阶段之间的数据传递都是⼀个流式的过程。每⼀个Operator对⼀⾏数据完成操作后之后将数据传递给childOperator计算。
Operator类的主要属性和⽅法如下
RowSchema表⽰Operator的输出字段
InputObjInspector outputObjInspector解析输⼊和输出字段
processOp接收⽗Operator传递的数据,forward将处理好的数据传递给⼦Operator处理
Hive每⼀⾏数据经过⼀个Operator处理之后,会对字段重新编号,colExprMap记录每个表达式经过当前Operator处理前后的名称对应关系,在下⼀个阶段逻辑优化阶段⽤来回溯字段名
由于Hive的MapReduce程序是⼀个动态的程序,即不确定⼀个MapReduce Job会进⾏什么运算,可能是Join,也可能是GroupBy,所以Operator将所有运⾏时需要的参数保存在OperatorDesc 中,OperatorDesc在提交任务前序列化到HDFS上,在MapReduce任务执⾏前从HDFS读取并反序列化。Map阶段 OperatorTree在HDFS上的位置在Conf(“plan”)
+ “/l”
QueryBlock⽣成Operator Tree
QueryBlock⽣成Operator Tree就是遍历上⼀个过程中⽣成的QB和QBParseInfo对象的保存语法的属性,包含如下⼏个步骤:
QB#aliasToSubq => 有⼦查询,递归调⽤
QB#aliasToTabs => TableScanOperator
QBParseInfo#joinExpr => QBJoinTree => ReduceSinkOperator + JoinOperator
QBParseInfo#destToWhereExpr => FilterOperator
QBParseInfo#destToGroupby => ReduceSinkOperator + GroupByOperator
QBParseInfo#destToOrderby => ReduceSinkOperator + ExtractOperator
由于Join/GroupBy/OrderBy均需要在Reduce阶段完成,所以在⽣成相应操作的Operator之前都会先⽣成⼀个ReduceSinkOperator,将字段组合并序列化为Reduce Key/value, Partition Key
接下来详细分析样例SQL⽣成OperatorTree的过程
先序遍历上⼀个阶段⽣成的QB对象
⾸先根据⼦QueryBlock
[Plain Text] 纯⽂本查看复制代码
1TableScanOperator(“dim.user”) TS[0] TableScanOperator(“detail.usersequence_client”) TS[1] TableScanOperator(“derpayment”) TS[2]
先序遍历QBJoinTree,类QBJoinTree保存左右表的ASTNode和这个查询的别名,最终⽣成的查询树如下 base / \ p du / \ c p
前序遍历detail.usersequence_client和
图中 TS=TableScanOperator RS=ReduceSinkOperator JOIN=JoinOperator
⽣成中间表与dim.user的Join操作树
根据QB2 FilterOperator。此时QB2遍历完成。
下图中SelectOperator在某些场景下会根据⼀些条件判断是否需要解析字段。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论