合理设置Map任务个数

1. 一般情况下,作业会根据input目录产生一个或者多个map任务。

map任务的个数主要由如下因素决定

a. input文件总个数

b. input文件的大小

c. 集群设置的文件块大小 (目前为128M,该参数不能自定义修改)

2. 请举例说明文件切分方式

a. 假设input目录下有1个文件 c , 其大小为680M, hadoop会将该文件 c 切分为6个块(1个40M的和5个128M大小的块),对应的map数为6。

b. 假设input目录下有4个文件a , b , c, d , 它们的大小分别为5M, 10M, 128M, 140M,那么hadoop会将其切分为5个块(5个块的大小分别为5M, 10M, 128M, 128M, 12M) ,对应的Map数是,即如果文件大于块大小(128M),会进行拆分,如果小于块大小,则把该文件当成一个块进行处理。

3. map数是否越多越好?

map数并非越多越好,如果一个任务包含很多小文件(远远小于所设置的块大小),那么每个小文件都会被被当做一个独立的块且对应一个map。在上面这种情况下,map任务启动和初始化的时间远远大于逻辑处理的时间,造成很大的资源浪费。

4. 是不是保证每个map处理接近128M的文件块,就可以达到最好的性能 ?

答案是不一定。例如一个120m的文件,正常情况下会用一个map去读取,如果这个文件块内每行只有一个或者两个小字段,但有数千万级别的记录,如果这个时候map处理的逻辑比较负责的话,用一个map任务去做处理,任务整体执行耗时还是不小的。

针对3, 4 所描述的问题,可以通过如下两种方式来解决:

a. 减少map数

如何合并小文件,减少map数?

假设一个SQL任务:


select count(1) from tmp1  where pt='2020-10-10'


该任务的inputdir /xxx/pt=2020-10-10共有300个文件,其中很多是远远小于128M的小文件,总大小10G,正常执行时会启动300个map任务。

Map总共消耗的计算资源数 SLOTS_MILLIS_MAPS= 623,020。

可以通过配置如下参数来在map执行前合并小文件,减少map数:


set mapred.max.split.size=100000000; 
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;


再执行上面的语句,用了74个map任务,map消耗的计算资源:SLOTS_MILLIS_MAPS= 333,500。

对于这个简单SQL任务,执行时间上可能差不多,但节省了一半的计算资源。

上面的参数的值为100000000 对应100MB

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; 这个参数表示执行前进行小文件合并,前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分割大文件剩下的)进行合并,最终生成了74个块。

如何适当的增加map数?

当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。

假设有这样一个任务:


select age, 
count(1), 
count(distinct user_id), 
sum(case when …), 
sum(…) 
from tmp2  group by age。。


如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务,肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个,这样就可以用多个map任务去完成,具体实现样例如下:


set mapred.reduce.tasks=10; 
create table tmp3 as 
select * from a
distribute by rand(20);


这样会将a表的记录,随机的分散到包含20个文件的tmp3表中,再用tmp3 代替上面sql中的a 表,则会用20个map任务去完成。每个map任务处理6M左右(数百万记录)的数据,效率会提高不少。

增加Map数与减少Map数,一个是要把大文件拆成小文件,一个是要合并小文件,听起来是矛盾的,但是这个也正是需要开发者重点关注的地方,开发者需要根据实际的数据情况, 合理的控制Map的个数。整体上要保证单个map任务处理的数据量适中且任务数据吞吐量合理。


合理设置reduce任务个数

  1. Hive自己如何确定reduce数?

reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定:


hive.exec.reducers.bytes.per.reducer -- (每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max -- (每个任务最大的reduce数,默认为999)


Reduce 个数 N = min(参数2,总输入数据量/参数1)

即,如果reduce的输入(map的输出)总大小不超过1G, 那么只会创建一个reduce任务;

2. 如何调整reduce个数?

a. 调整hive.exec.reducers.bytes.per.reducer参数的值;

set hive.exec.reducers.bytes.per.reducer=500000000; (500M)

b. set mapred.reduce.tasks = 15;

如何在命令中明确配置reduce个数,那么hive就不会推测reduce个数,而是直接创建15个reduce任务。

3. reduce个数是不是越多越好?

答案是否定的,与map一样, reduce的启动同样会耗费时间与计算资源。 另外reduce的个数决定着输出文件个数,如何reduce处理之后生成了很多小文件,如果不经过处理就传递给下游的话,又会出现小文件过多的问题。

4. 什么场景下只会创建单个reduce?

除了数据量小于 hive.exec.reducers.bytes.per.reducer 参数所设定的值之外,还有如下几个原因会导致只创建了一个reduce任务,在如下的几个原因:

a. 汇总SQL中忽略group by 算子

下面两个SQL 得到的统计量结果是相同的,但是执行效率是不同的。其中SQL1由于没有group by 算子,故进行数据量统计时,只会使用一个reduce任务进行执行。


SQL1:  select count(1) from table where dt= '2020-10-04'; 
SQL2:  select dt,count(1) from table where dt= '2020-10-04' group by dt;


上面这种情况大家经常会遇到,一定要合理的改写。

b. 使用了Order by

由于order by 会做全局的排序,只会创建一个reduce任务.

c. 出现笛卡尔积

上面这些情况下,尽量避免,由于上面这些操作都是全局的,所以hadoop不得不用单个reduce去完成。

另外,在设置reduce个数的时候同样需要考虑如下两个原则:即大数据量情况下配置合适的reduce数;单个reduce任务处理合适的数据量。