一、控制hive任务中的map数:
通常情况下,作业会通过input的目录产生一个或者多个map任务。 主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改); 举例:
a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数。 b) 假设input目录下有3个文件a,b,c,大小分别为10m,20m,150m,那么hadoop会分隔成4个块(10m,20m,128m,22m),从而产生4个map数 即如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。
如果超过128m就一定会被拆分吗?
不一定 如果是130m,仍然只会产生1个map 允许有10%的溢出:如果文件大小超过分片大小10%以内,作为一个分片
具体原因可以参考这篇文章
是不是map数越多越好?
答案是否定的。 如果一个任务有很多小文件(远远小于块大小128m
),则每个小文件也会被当做一个块,用一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费
。而且,同时可执行的map数是受限的。
那么针对这种情况,如何合并小文件,减少map数?
Select count ( 1 )
from xxx
where pt = ‘2020 - 08 - 11 ’;
该表的分区目录下共有194个文件,其中很多是远远小于128m的小文件,总大小9G,正常执行会用194个map任务。 可以通过以下方法来在map执行前合并小文件,减少map数
: 方式1:输入合并,即在Map前合并小文件,下面的数值需自行调整
set hive. hadoop. supports. splittable. combineinputformat= true ;
set hive. input. format= org. apache. hadoop. hive. ql. io. CombineHiveInputFormat;
set mapred. min. split. size= 100000000 ;
set mapred. max. split. size= 100000000 ;
set mapred. min. split. size. per. node= 50000000 ;
set mapred. min. split. size. per. rack= 50000000 ;
解释
100000000表示100M set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat
;这个参数表示执行前进行小文件合并
过期的对应配置可以查询:https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/DeprecatedProperties.html mapred.min.split.size相当于mapreduce.input.fileinputformat.split.minsize,设置哪一个都一样 mapred.max.split.size相当于mapreduce.input.fileinputformat.split.maxsize,设置哪一个都一样 所以根据求splitSize的公式,return Math.max(minSize, Math.min(maxSize, blockSize));
可以知道,需增加mapper数,则减少这些值;需减少mapper数,则增加这些值,一般就配置这两个值就行了
。 一般来说
max.split.size >= min.split.size >= min.size.per.node >= min.size.per.node 优先级
当四个参数设置矛盾时,系统会自动以优先级最高的参数为准,进行计算 max.split.size <= min.split.size <= min.size.per.node <= min.size.per.node 方式2:结果合并,下面的数值需自行调整
set hive. merge. mapfiles= true ; -- 在Map - only的任务结束时合并小文件
set hive. merge. mapredfiles= true ; -- 在Map - Reduce 的任务结束时合并小文件
set hive. merge. size. per. task= 128000000 ; -- 合并后文件的大小为128 M左右
set hive. merge. smallfiles. avgsize= 128000000 ; -- 当输出文件的平均大小小于128 M时,启动一个独立的map- reduce任务进行文件merge
Hive在对结果文件进行合并时会执行一个额外的map-only脚本,mapper的数量是文件总大小除以size.per.task参数所得的值
是不是保证每个map处理接近128m的文件块,就高枕无忧了?
答案也是不一定。 比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时
。
那么针对这种情况,如何适当增加map数?
当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。 假设有这样一个任务:
Select data_desc,
count ( 1 ) ,
count ( distinct id) ,
sum ( case when …) ,
sum ( case when …) ,
sum ( …)
from a group by data_desc
如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务,肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个,这样就可以用多个map任务去完成。
set mapred. reduce. tasks= 10 ;
map的最终个数取决于goalsize=min(totalsize/mapred.reduce.tasks,块大小)
,goalsize就相当于splitsize 但是往往这个是不生效的
如果hive处理的文件是压缩模式,且压缩模式不支持文件切分,那么这个时候我们只能通过控制参数来减少map个数,而不能通过配置参数来增加map个数,所以Hive对于压缩不可切分文件的调优有限
。 那么我们可以利用中间表
create table a_1 as
select * from a
distribute by CAST( RAND( ) * 10 AS INT ) ;
这样会将a表的记录,随机的分散
到包含10个文件的a_1表中,再用a_1代替上面sql中的a表,则会用10个map任务去完成。每个map任务处理大于12M(几百万记录)的数据,效率肯定会好很多。 看上去,貌似这两种有些矛盾,一个是要合并小文件,一个是要把大文件拆成小文件,这点正是重点需要关注的地方,根据实际情况,控制map数量需要遵循两个原则:使大数据量利用合适的map数;使单个map任务处理合适的数据量
。
二、 控制hive任务的reduce数:
Hive自己如何确定reduce数:
reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定: hive.exec.reducers.bytes.per.reducer
(每个reduce任务处理的数据量,默认为1000^3=1G)hive.exec.reducers.max
(每个任务最大的reduce数,默认为999)计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务; 例如下图,(67108864/1024/1024=64m)
,可以推测一个130m的输入数据量会启动reducer数=min(1099,130m/64m)=3
个reduce
reduce个数多好还是少好?
同map一样,启动和初始化reduce也会消耗时间和资源;
另外,有多少个reduce,就会有多少个输出文件
,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
如何调整reduce个数?
调整reduce个数方法一:
调整hive.exec.reducers.bytes.per.reducer参数的值; 500000000即(500M)
set hive. exec. reducers. bytes. per. reducer= 500000000 ;
set mapred. reduce. tasks = 15 ;
什么情况下只有一个reduce?
很多时候你会发现任务中不管数据量多大,不管你有没有设置调整reduce个数的参数,任务中一直都只有一个reduce任务
;其实只有一个reduce任务的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外
,还有以下原因:
a) 没有group by的汇总
比如把select pt,count(1) from table_xxx where pt = '2020-08-11' group by pt;
写成 select count(1) from table_xxx where pt = '2020-08-11';
b) 用了Order by
c) 有笛卡尔积
通常这些情况下,除了找办法来变通和避免,我暂时没有什么好的办法,因为这些操作都是全局的,所以hadoop不得不用一个reduce去完成; 同样的,在设置reduce个数的时候也需要考虑这两个原则:使大数据量利用合适的reduce数;使单个reduce任务处理合适的数据量
。