HiveSQL的编译过程:美团数据仓库(转)Hive是基于Hadoop的一个数据仓库系统,在各大公司都有广泛的应用。美团数据仓库也是基于Hive搭建,每天执行近万次的HiveETL计算流程,负责每天数百GB的数据存储和分析。Hive的稳定性和性能对我们的数据分析非常关键。在几次升级Hive的过程中,我们遇到了一些大大小小的问题。通过向社区的咨询和自己的努力,在解决这些问题的同时我们对Hive将SQL编译为MapReduce的过程有了比较深入的理解。对这一过程的理解不仅帮助我们解决了一些Hive的bug,也有利于我们优化HiveSQL,提升我们对Hive的掌控力,同时有能力去定制一些需要的功能。MapReduce实现基本SQL操作的原理详细讲解SQL编译为MapReduce之前,我们先来看看MapReduce框架实现SQL基本操作的原理Join的实现原理selectu.name,o.orderidfromorderojoinuseruono.uid=u.uid;在map的输出value中为不同表的数据打上tag标记,在reduce阶段根据tag判断数据来源。MapReduce的过程如下(这里只是说明最基本的Join的实现,还有其他的实现方式)GroupBy的实现原理selectrank,isonline,count(*)fromcitygroupbyrank,isonline;将GroupBy的字段组合为map的输出key值,利用MapReduce的排序,在reduce阶段保存LastKey区分不同的key。MapReduce的过程如下(当然这里只是说明Reduce端的非Hash聚合过程)Distinct的实现原理selectdealid,count(distinctuid)numfromordergroupbydealid;当只有一个distinct字段时,如果不考虑Map阶段的HashGroupBy,只需要将GroupBy字段和Distinct字段组合为map输出key,利用mapreduce的排序,同时将GroupBy字段作为reduce的key,在reduce阶段保存LastKey即可完成去重HiveSQL的编译过程:美团数据仓库(转)2016年2月1日16:00分区hive优化的第1页如果有多个distinct字段呢,如下面的SQLselectdealid,count(distinctuid),count(distinctdate)fromordergroupbydealid;实现方式有两种:(1)如果仍然按照上面一个distinct字段的方法,即下图这种实现方式,无法跟据uid和date分别排序,也就无法通过LastKey去重,仍然需要在reduce阶段在内存中通过Hash去重(2)第二种实现方式,可以对所有的distinct字段编号,每行数据生成n行数据,那么相同字段就会分别排序,这时只需要在reduce阶段记录LastKey即可去重。这种实现方式很好的利用了MapReduce的排序,节省了reduce阶段去重的内存消耗,但是缺点是增加了shuffle的数据量。需要注意的是,在生成reducevalue时,除第一个distinct字段所在行需要保留value值,其余distinct数据行value字段均可为空。SQL转化为MapReduce的过程了解了MapReduce实现SQL基本操作之后,我们来看看Hive是如何将SQL转化为MapReduce任务的,整个编译过程分为六个阶段:1.Antlr定义SQL的语法规则,完成SQL词法,语法解析,将SQL转化为抽象语法树ASTTree2.遍历ASTTree,抽象出查询的基本组成单元QueryBlock3.遍历QueryBlock,翻译为执行操作树OperatorTree4.逻辑层优化器进行OperatorTree变换,合并不必要的ReduceSinkOperator,减少shuffle数据量5.遍历OperatorTree,翻译为MapReduce任务6.物理层优化器进行MapReduce任务的变换,生成最终的执行计划下面分别对这六个阶段进行介绍Phase1SQL词法,语法解析AntlrHive使用Antlr实现SQL的词法和语法解析。Antlr是一种语言识别的工具,可以用来构造领域语言。这里不详细介绍Antlr,只需要了解使用Antlr构造特定的语言只需要编写一个语法文件,定义词法和语法替换规则即可,Antlr完成了词法分析、语法分析、语义分析、中间代码生成的过程。Hive中语法规则的定义文件在0.10版本以前是Hive.g一个文件,随着语法规则越来越复杂,由语法规则生成的Java分区hive优化的第2页即可,Antlr完成了词法分析、语法分析、语义分析、中间代码生成的过程。Hive中语法规则的定义文件在0.10版本以前是Hive.g一个文件,随着语法规则越来越复杂,由语法规则生成的Java解析类可能超过Java类文件的最大上限,0.11版本将Hive.g拆成了5个文件,词法规则HiveLexer.g和语法规则的4个文件SelectClauseParser.g,FromClauseParser.g,IdentifiersParser.g,HiveParser.g。抽象语法树ASTTree经过词法和语法解析后,如果需要对表达式做进一步的处理,使用Antlr的抽象语法树语法AbstractSyntaxTree,在语法分析的同时将输入语句转换成抽象语法树,后续在遍历语法树时完成进一步的处理。下面的一段语法是HiveSQL中SelectStatement的语法规则,从中可以看出,SelectStatement包含select,from,where,groupby,having,orderby等子句。(在下面的语法规则中,箭头表示对于原语句的改写,改写后会加入一些特殊词标示特定语法,比如TOK_QUERY标示一个查询块)selectStatement:fromClauseselectClausegroupByClause?whereClause?orderByClause?havingClause?distributeByClause?clusterByClause?sortByClause?limitClause?-^(TOK_QUERYfromClause^(TOK_INSERT^(TOK_DESTINATION^(TOK_DIRTOK_TMP_FILE))selectClausewhereClause?groupByClause?havingClause?orderByClause?clusterByClause?;distributeByClause?sortByClause?limitClause?))样例SQL为了详细说明SQL翻译为MapReduce的过程,这里以一条简单的SQL为例,SQL中包含一个子查询,最终将数据写入到一张表中FROM(SELECTp.datekeydatekey,p.useriduserid,FROMc.clienttypeJOINfact.orderpaymentpONp.orderid=c.orderiddetail.usersequence_clientcWHEREp.datekey=20131118JOINdefault.userduONdu.userid=p.userid)basebase.clienttype,INSERTOVERWRITETABLE`test`.`customer_kpi`SELECTbase.datekey,GROUPBYbase.datekey,base.clienttypecount(distinctbase.userid)buyer_countSQL生成ASTTreeAntlr对HiveSQL解析的代码如下,HiveLexerX,HiveParser分别是Antlr对语法文件Hive.g编译后自动生成的词法解析和语法解析类,在这两个类中进行复杂的解析。HiveLexerXlexer=newHiveLexerX(newANTLRNoCaseStringStream(command));//词法解析,忽略关键词的大小写TokenRewriteStreamtokens=newTokenRewriteStream(lexer);if(ctx!=null){HiveParserparser=newHiveParser(tokens);//语法解析ctx.setTokenRewriteStream(tokens);}parser.setTreeAdaptor(adaptor);HiveParser.statement_returnr=null;try{分区hive优化的第3页try{}catch(RecognitionExceptione){r=parser.statement();//转化为ASTTreee.printStackTrace();thrownewParseException(parser.errors);}最终生成的ASTTree如下图右侧(使用AntlrWorks生成,AntlrWorks是Antlr提供的编写语法文件的编辑器),图中只是展开了骨架的几个节点,没有完全展开。子查询1/2,分别对应右侧第1/2两个部分。这里注意一下内层子查询也会生成一个TOK_DESTINATION节点。请看上面SelectStatement的语法规则,这个节点是在语法改写中特意增加了的一个节点。原因是Hive中所有查询的数据均会保存在HDFS临时的文件中,无论是中间的子查询还是查询最终的结果,Insert语句最终会将数据写入表所在的HDFS目录下。详细来看,将内存子查询的from子句展开后,得到如下ASTTree,每个表生成一个TOK_TABREF节点,Join条件生成一个“=”节点。其他SQL部分类似,不一一详述。Phase2SQL基本组成单元QueryBlockASTTree仍然非常复杂,不够结构化,不方便直接翻译为MapReduce程序,ASTTree转化为QueryBlock就是将SQL进一部抽象和结构化。QueryBlockQueryBlock是一条SQL最基本的组成单元,包括三个部分:输入源,计算过程,输出。简单来讲一个QueryBlock就是一个子查询。下图为Hive中QueryBlock相关对象的类图,解释图中几个重要的属性•QB#aliasToSubq(表示QB类的aliasToSubq属性)保存子查询的QB对象,aliasToSubqkey值是子查询的别名•QB#qbp即QBParseInfo保存一个基本SQL单元中的给个操作部分的ASTTree结构,QBParseInfo#nameToDest这个HashMap保存查询单元的输出,key的形式是inclause-i(由于Hive支持MultiInsert语句,所以可能有多个输出),value是对应的ASTNode节点,即TOK_DESTINATION节点。类QBParseInfo其余HashMap属性分别保存输出和各个操作的ASTNode节点的对应关系。分区hive优化的第4页个操作的ASTNode节点的对应关系。•QBParseInfo#JoinExpr保存TOK_JOIN节点。QB#QBJoinTree是对Join语法树的结构化。•QB#qbm保存每个输入表的元信息,比如表在HDFS上的路径,保存表数据的文件格式等。•QBExpr这个对象是为了表示Union操作。ASTTree生成QueryBlockASTTree生成QueryBlock的过程是一个递归的过程,先序遍历ASTTree,遇到不同的Token节点,保存到相应的属性中,主要包含以下几个过程•TOK_QUERY=创建QB对象,循环递归子节点•TOK_FROM=将表名语法部分保存到QB对象的aliasToTabs等属性中•TOK_INSERT=循环递归子节点