HiveQL是一种声明式语言,用户会提交查询,而hive将其转换成MapReduce job,大多数情况我们不需要了解hive的内部工作,内部复杂的查询解析、优化和执行过程大部分时间我们是可以忽视的,不过想要彻底的掌握hive,我们就需要学习hive的理论知识以及底层的实现,这样会让用户更加高效的使用hive。
对于explain,可以帮助我们学习hive是如何将查询转换成MapReduce任务的。
如:
explain select sum(id) from number
在一个普通的查询语句前添加explain关键字,查询语句是不会执行的,它会打印出抽象语法树:
在图中我们可以看到有表名number,列名id,还有sum函数等,一个hive任务包含有多个stage阶段,不同的stage会存在依赖,查询越复杂通常stage会越多,通常也会启动更多的任务来完成。
stage plan的部分比较冗长也比较复杂,stage-1包含了这个job大部分的处理过程,而且会出发一个MapReduce,TableScan以这个表作为输入,然后产生一个id字段的输出,Group By Operator 会应用到sum(id),然后会产生一个输出的字段_col0(这是为临时结果字段按规则起的临时名字),这些都是map端的。而reduce这边,也就是Reduce Operator Tree下面,也有相同的Group By Operator,但是这次得到的是_col0字段进行的sum操作,最后我们看到了File Output Operator,说明输出是文本格式,是基于字符串输出格式:HiveIgnoreKeyTextOutputFormat
最后,因为该job没有limit,所以stage-0阶段没有任何操作
大家在用SQL语句的时候limit语句是经常用到的,有时候对大量数据,我们并不需要把所有数据查询出来,所以我们会用到limit来限制行数,返回部分结果。通常情况下limit还是要先执行整条查询语句再返回部分结果,这种情况通常是浪费的,所以我们应该尽可能的避免,hive有一个配置属性的开启,当使用limit语句时,可以对其数据进行抽样
hive.limit.optimize.enble
true
一旦 hive.limit.optimize.enble 属性设置为true时,那么将会有两个参数控制这个操作:
hive.limit.row.max.size
100000
hive.limit.optimize.limit.file
10
hive.limit.row.max.size:设置最小采样容量
hive.limit.optimize.limit.file:最大采样样本数
该功能有一个缺点就是,有可能有部分数据永远不会被处理掉。
hive中如果是多个表查询,它会将前面的表缓存起来,然后扫描最后一个表,因此我们通常需要表大的那个表放到后边,或者我们可以用/*streamtable(table_name) */ 指出哪个是大表。
当有三个及以上的表连接时,如果每个on子句都使用相同的连接键时,只会产生一个MapReduce job。
尽早的过滤不需要的数据,减少每个阶段的数据量,同时只选用能使用到的数据,对于分区表要加分区。
尽量避免一个SQL包含复杂逻辑,可以使用中间表来处理复杂的逻辑
如果所有表中,有一张表是小表,那么可以在大表通过mapper时,将小表完全放到内存,Hive可以在map端执行连接过程,这是因为Hive可以和内存中的小表进行逐一匹配,从而可以省略掉常规连接操作所欲要的reduce过程。该操作不仅减少了reduce过程,而且有时候还可以同时减少map过程的执行步骤。
有时候hive的输入数据量是很小的,这种情况下,为查询触发执行任务的时间消耗可能比实际job执行的时间多得多。对于大多数这种情况,hive可以通过本地模式在单台机器上处理所有任务,对于小数据集,执行时间会明显缩短。
set hive.exec.mode.local.auto = true
当一个job满足如下条件时,才能真正执行本地模式:
1).job的输入数据大小必须小于参数:hive.exec.mode.local.auto.inputbytes.max(默认128MB)
2).job的map数必须小于参数:hive.exec.mode.local.auto.tasks.max(默认4)
3).job的reduce数必须为0或者1
可用参数hive.mapred.local.mem(默认0)控制child jvm使用的最大内存数。
hive会将一个查询转换成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段等。默认情况下,hive一次只会执行一个阶段,不过某些特定的job可能包含多个阶段,而这些阶段也并非完全依赖,也就是说有的阶段是可以并行执行的,这样可以使整job的执行时间缩短。
开启并发执行:
set hive.exec.parallel=true;
同一个SQL允许的最大并行度,默认为8:
set hive.exec.parallel.thread.number=16
Hive提供了一个严格模式,可以防止用户执行那些可能产生意想不到的不好的影响的查询。
严格模式设置属性为strict可以禁止3种类型的查询
set hive.mapred.mode = strict
1).对于分区表,不加分区过滤字段不能查询
2).对于order by语句,必须使用limit
3).限制笛卡尔积的查询(join的时候不使用on而使用where)
Hive通过将查询划分成一个或者多个MapReduce任务达到并行的目的。每个任务都可能具有多个mapper和reducer任务,其中一些是可以并行执行的。确定最佳的mapper和reducer个数取决于多个变量
I.map执行时间 = map任务启动和初始化时间+逻辑处理时间
通常情况下,作业会通过input产生一个或多个map,主要决定因素有文件大小、文件个数、还有集群设置的文件块大小(默认128M)
如:一个文件如果有129M,那么集群会将文件拆分成128M和1M两个块,所以会产生两个map,如果有两个文件大小分别为1M,2M那么集群也会产生两个map。
II.map并不是越多越好
如果一个任务有很多远小于128M的文件,而且每个文件都生成一个map,那么map启动任务和初始化的时间远远高于逻辑处理的时间,会造成很大的资源浪费,而且可执行的map的数量是有限的,如果一个文件大小接近128M,但是处理的业务逻辑十分复杂,也不适合只用一个map去执行
针对以上问题我们可以选择适当增减map的数量:
减少map数量,即合并大量小文件:
set mapred.max.split.size=100000000;//100M
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
这个参数表示执行前进行小文件合并,
前面三个参数确定了合并文件的大小,大于128M的按照128M分隔,大于100M小于128M的按照100M分隔,小于100M的和那些大文件分隔后留下的小文件一起合并
如何增加map个数?
当单个文件比较大,业务逻辑比较复杂的时候,map会执行的比较慢,所以这个时候可以考虑考虑增加map来工作,可以考虑将文件合理拆分成多个
set mapred.reduce.tasks=10;
I.如何确定reduce的个数
reduce的个数极大的影响着,不指定reduce个数的情况下,hive会猜测确定一个reduce的个数,基于以下设定:
hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为999)
即如果map的输出,reduce的输入不超过1g的话则会有一个reduce任务
调整reduce个数的方法:
set hive.exec.reducers.bytes.per.reducer=500000000; (500M)//设置每个reduce处理任务的数据量
set mapred.reduce.tasks = 15; //直接设置reduce的个数
II.只有一个reduce的情况
a.SQL语句中没有group by的汇总
b.用了order by
c.使用笛卡尔积
jvm重用对hive性能具有非常大的影响,特别是对很难避免小文件的场景或者task特别多的场景,这类场景大多数执行时间都很短,因为hive调起MapReduce任务,JVM启动过程会造成很大开销,JVM重用会使JVM实例再同一个job中使用多次
设置jvm重用个数:
set mapred.job.reuse.jvm.num.tasks=10; --10为重用个数
缺点:开启JVM重用将会一直占用使用到的task插槽,以便重用,知道任务完成才能释放,如果某个job中的reduce任务比其他的reduce任务花的时间要长的多的话,那么保留的插槽就会一直空着无法被其他job使用,知道reduce任务执行结束。
----开启动态分区功能:
hive.exec.dynamic.partition=true;
----必须保证有一个分区是静态的:
hive.exec.dynamic.partition.mode=strict;//设置为nonstrict则所有分区都是动态的
----每个mapper或reducer可创建的最大分动态区数:
hive.exec.max.dynamic.partitions.pernode=100;
----一个动态分区创建语句可创建的最大动态分区数:
hive.exec.max.dynamic.partitions=1000;
----全局可创建的最大文件个数:
hive.exec.max.created.files=100000;
----控制DataNode一次可打开的文件个数:(在hdfs-site.xml中设置)
dfs.datanode.max.xcievers
8192
推测执行是Hadoop中的一个功能,可以触发执行一些重复的任务,尽管这样会重复对数据进行计算而消耗更多的计算资源,不过这个功能的目标是通过加快获取单个task的结果以及将执行慢的TaskTracker加入到黑名单的方式来提高整体的效率。
(1)修改 $HADOOP_HOME/conf/mapred-site.xml文件
mapred.map.tasks.speculative.execution
true
mapred.reduce.tasks.speculative.execution
true
(2)修改hive配置
set hive.mapred.reduce.tasks.speculative.execution=true;
一个特别的优化试图将查询中的多个Group By 操作组装到单个MapReduce任务中,如果想启动这个优化,需要一组常用的Group By 键:
hive.multigroupby.singlemr
false
Hive提供了两种虚拟列:一种用于将要进行划分的输入文件名,另一种用于文件中的块内偏移量。当hive产生了非预期的或null的返回结果时,可以通过这些虚拟列诊断查询。通过查询这些“字段”,用户可以查看到哪个文件甚至哪行数据导致的问题
set hive.exec.rowoffset = true
当一些任务执行进度长期维持在99%或100%,查看任务监控页面,发现只有几个少量的reduce子任务未完成,因为其处理的数据量和其他子任务差异过大,单一的reduce任务数据量和平均数据量差异过大,所以时长远大于平均时长。
可能原因:
1).key之分布不均匀
2).业务数据本身特点
3).建表时考虑不周
4).某些SQL语句本身就有数据倾斜
参数调节:
hive.map.aggr=true
在Map端做combiner,假如map各条数据基本上不一样, 聚合没什么意义,做combiner反而画蛇添足,hive里也考虑的比较周到通过参数hive.groupby.mapaggr.checkinterval = 100000 (默认)hive.map.aggr.hash.min.reduction=0.5(默认),预先取100000条数据聚合,如果聚合后的条数/100000>0.5,则不再聚合
hive.groupby.skewindata=true
生成两个MR Job,第一个MR Job Map的输出结果随机分配到reduce做次预汇总,减少某些key值条数过多某些key条数过小造成的数据倾斜问题