过程:
1.用户通过命令行CLI或其他Hive访问工具,向Hive输入一段命令或sql查询,由Hive驱动模块中的编译器——Antlr语言识别工具,对用户输入的SQL语言进行词法和语法解析,将SQL语句转化为抽象语法树AST Tree的形式。
2.遍历AST Tree,抽象出查询的基本组成单元QueryBlock,其中QueryBlock是一条最基本的SQL语法组成单元,包括输入源、计算过程和输出三部分。
3.再对QueryBlock进行遍历,生成执行操作树(OperatorTree)。其中,OperatorTree由很多逻辑操作符组成,如TableScanOperator,SelectOperator,FilterOperator,JoinOperator,GroupByOperator,ReduceSinkOperator等。这些逻辑操作符可以在Map阶段和Reduce阶段完成某一特定操作。
4.通过Hive驱动模块中的逻辑优化器对OperatorTree进行优化。变换OperatorTree的形式,来合并多余的操作符减少MapReduce任务数量以及Shuffle阶段的数据量。
5.对优化后的OperatorTree进行遍历,根据OperatorTree中的逻辑操作符翻译成需要执行的MapReduce任务。
6.启动Hive驱动模块中的物理优化器,对生成MapReduce任务进行优化,生成最终的MapReduce任务执行计划,由Hive驱动模块中的执行器,执行最终的MapReduce任务。
Join的实现原理:
select s.sname, c.cname from class c join student s on c.uid = s.uid;
在map的输出value中为不同表的数据打上tag标记,在reduce阶段根据tag判断数据来源。MapReduce的过程如下:
Common Join:
适用场景:适用于所有类型的表关联与其他类型join不支持的join类型,比如:full outer join
Map阶段:读取源表的数据,Map输出时候以Join on条件中的列为key,如果Join有多个关联键,则以这些关联键的组合作为key;Map输出的value为join之后所关心的(select或者where中需要用到的)列,同时在value中还会包含表的Tag信息,用于标明此value对应哪个表。
Shuffle阶段:根据key的值进行hash,并将key/value按照hash值推送至不同的reduce中,这样确保两个表中相同的key位于同一个reduce中。
Reduce阶段:根据key的值完成join操作,期间通过Tag来识别不同表中的数据。
Map Join:
适用场景:小表(维度表)join大表(事实表),不适用与Right/Full outer join.
如果关联的表足够小,那么可以将小表加载到mapper的内存中,在map端完成join,减少shuffle和reduce阶段。MapReduce Local Task会在真正的MapReduce Join Task之前,从HDFS读取小表,然后将其转成一个tar文件,最后将文件上传至HDFS Cache.MapReduce Local Task运行过程中,可能由于内存不足而失败,可以通过设置hive.mapjoin.localtask.max.memory.usage
来改变Local Task可使用的内存大小。
Group By的实现原理:
select uid,city, count(*) from student group by uid,city;
MapReduce因为要从多个文件块中拉取表数据,因此会有shuffle sort这一步
Map端聚合: Map端进行预聚合,减少shuffle数据量,类似于MR中的Combiner。默认情况下,Hive 会尽可能地使用 Map 端Aggregation,但是如果 Hash Map不能有效地降低内存使用,那么会降级到普通的Aggregation,即 Map 端仅做Shuffle Write,Reducer执行真正的聚合运算。
倾斜: 生成的查询计划有两个 MapReduce 任务。在第一个 MapReduce 中,map 的输出结果集合会随机分布到 reduce 中, 每个 reduce 做部分聚合操作,并输出结果。这样处理的结果是,相同的 Group By Key 有可 能分发到不同的 reduce 中,从而达到负载均衡的目的;第二个 MapReduce 任务再根据预处 理的数据结果按照 Group By Key 分布到 reduce 中(这个过程可以保证相同的 Group By Key 分布到同一个 reduce 中),最后完成最终的聚合操作。
Distinct的实现原理:
select city, count(distinct uid) from student group by city;
- 第一步先在mapper计算部分值,会以city和uid作为key,如果是distinct并且之前已经出现过,则忽略这条计算。第一步是以组合为key,第二步是以city为key.
- ReduceSink是在mapper.close()时才执行的,在GroupByOperator.close()时,把结果输出。注意这里虽然key是city和uid,但是在reduce时 分区是按city来的.
- 第一步的distinct计算的值没用,要留到reduce计算的才准确。这里只是减少了key组合相同的行。
- distinct通过比较lastInvoke判断要不要+1(因为在reduce是排序过了的,所以判断distict的字段变了没有,如果没变,则不+1)
Explain执行计划:
hive> explain select city, count(distinct uid) from student group by city;
OK
ABSTRACT SYNTAX TREE:
(TOK_QUERY (TOK_FROM (TOK_TABREF (TOK_TABNAME student))) (TOK_INSERT (TOK_DESTINATION (TOK_DIR TOK_TMP_FILE)) (TOK_SELECT (TOK_SELEXPR (TOK_TABLE_OR_COL city)) (TOK_SELEXPR (TOK_FUNCTIONDI city (TOK_TABLE_OR_COL uid)))) (TOK_GROUPBY (TOK_TABLE_OR_COL city))))
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://Map阶段
student
TableScan //TableScan以student表作为输入
alias: student
Select Operator//列裁剪,取出uid,city字段
expressions:
expr: city
type: string
expr: uid
type: string
outputColumnNames: city, uid
Group By Operator //map聚集
aggregations:
expr: city(DISTINCT uid) //group by Operator 的聚集表达式
bucketGroup: false
keys:
expr: city
type: string
expr: uid
type: string
mode: hash //hash方式
outputColumnNames: _col0, _col1, _col2 //为临时结果字段按规则起的临时字段名
Reduce Output Operator
key expressions: //输出的键
expr: _col0 //city
type: string
expr: _col1 //uid
type: string
sort order: ++
Map-reduce partition columns: //这里是按group by的字段分区的
expr: _col0 //这里表示city
type: string
tag: -1
value expressions:
expr: _col2
type: bigint
Reduce Operator Tree:
Group By Operator //第二次聚集
aggregations:
expr: city(DISTINCT KEY._col1:0._col0) //uid:city
bucketGroup: false
keys:
expr: KEY._col0 //city
type: string
mode: mergepartial //合并
outputColumnNames: _col0, _col1
Select Operator //列裁剪
expressions:
expr: _col0
type: string
expr: _col1
type: bigint
outputColumnNames: _col0, _col1
File Output Operator //输出结果到文件
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
相关参数
# 是否开启mapper端聚合
hive.map.aggr
# 是否开启,如果数据倾斜,是否优化group by为两个MR job
#该配置会触发hive增加额外的mr过程,随机化key后进行聚合操作得到中间结果,再对中间结果执行最终的聚合操作。
#count(distinct)操作比较特殊,无法进行中间的聚合操作,因此该参数对有count(distinct)操作的sql不适用。
hive.groupby.skewindata
# 用于map端聚合的hashtable最大可用内存,如果超过该内存比例,将flush到磁盘
hive.map.aggr.hash.force.flush.memory.threshold
# 可以用于mapper端hatable的内存比例
hive.map.aggr.hash.percentmemory (Default: 0.5) – Percent of total map task memory that can be used for hash table.
# 如果hashtable大小/输入行数 大于该阈值,那么停止hash聚合,转为sort-based aggregation
hive.map.aggr.hash.min.reduction (Default: 0.5)
# 每隔多少行,检测hashtable大小和input row比例是否超过阈值
hive.groupby.mapaggr.checkinterval
# 是否开启bucket group by
hive.optimize.groupby
# 是否自动转换common join为map join
set hive.auto.convert.join=true;
# 如果join的小表和小于该阈值,会尝试将Common join 转换成map join。通过explain命令,可以发现Operator树中有conditional Operator。 如果n-1张表大小和,小于该阈值,则生成conditional tasks。
hive.smalltable.filesize or hive.mapjoin.smalltable.filesize
# 如果join的小表小于该阈值,会直接将Common join转换成Map join。需要考虑到数据解压之后的实际大小,hive表在被解压后,文件大小可能会增大10倍。
hive.auto.convert.join.noconditionaltask.size
参考:
Hivesql的编译过程