Hive调优之map&reduce数目

map数目确定方法

Map数目的划分是由输入文件大小,个数等因素决定的,另外不同的文件输入格式切分map数目的方法也是不一样的,我们来看下Hive使用的两种输入格式的Map数目确定方法。

HiveInputFormat

MapTask的数目主要有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改),控制这些变量的参数如下:

set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
# 文件分割最大大小
set mapreduce.input.fileinputformat.split.maxsize=536870912; // goalSize
# 文件分割最小值
set mapreduce.input.fileinputformat.split.minsize=236870912; // minSize
# 块大小
set dfs.block.size=128M;  // blockSize

HiveInputFormat使用了FileInputFormat默认的getSplits方法会进行map划分,首先会计算目标分片大小:Math.max(minSize, Math.min(goalSize, blockSize)),然后对于每个文件,按照目标分片大小切分,不足一个目标分片大小的当做一个分片处理,所以当小文件比较多时候,就会产生很多map,我们看一下下面的两个实例:

  1. 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块<6个128m的块和1个12m的块>,从而产生7个map数;
  2. 假设input目录下有3个文件a, b, c,大小分别为10m,20m,130m,那么hadoop会分隔成4个块<10m,20m,128m,2m>,从而产生4个map数,即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。

所以,在HiveInputFormatgetSplits逻辑下,一个map task最多只能处理一个文件。也就是说,如果输入文件有n个,无论怎么调整参数,分配的map数量都不会少于n。在小文件比较多时候就会产生很多map,我们要想办法来进行小文件合并,CombineHiveInputFormat就是来处理这种情况的。

CombineHiveInputFormat

CombineHiveInputFormat的底层实现是使用了CombineFileInputFormatgetSplits方法,使用时候hadoop会进行小文件合并,还会尽量保证各个节点、机架都能分配到一定的数据。我们需要调节如下参数:

set mapred.max.split.size=100000000;  // 100M  maxSize 切片大小最大值
set mapred.min.split.size.per.node=100000000; // minSizePerNode 同一节点的数据块形成切片时,切片大小的最小值
set mapred.min.split.size.per.rack=100000000;  // minSizePerRack 同一机架的数据块形成切片时,切片大小的最小值
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;  // 表示执行前进行小文件合并

CombineHiveInputFormat的切分逻辑中,要遍历nodeToBlocks[每个node对应的blocks关系]下的所有node,然后继续遍历node下的所有block,将block的大小相加(这个block的大小计算也和maxSize有关),一直叠加到maxSize后生成一个分片,然后退出该node的block遍历。如果遍历完block都没达到maxSize,但是累加的数据量达到了minSizePerNode的值,也可以切割出一个分片来,否则就什么都不做。之后继续下一个node遍历,在这个过程中,一个node最多只会生成一个分片。之后按照基本一样的流程遍历rackToBlocks[每个rack对应的blocks关系],处理完rack的分片后将所有剩下的block会放到一个overflowBlocks中。最后一步是遍历overflowBlocks,不断累加block,遍历过程中达到maxSize就切割一个分片出来,直到全部遍历完。

所以可以看出来CombineHiveInputFormat在决定将哪些小文件的数据块“打包”为一个切片时会充分考虑数据本地性<节点本地性、机器本地性>的特性,对小文件进行合并。

map数据读取

介绍完Map数目确定方法后,就可以读取数据,每个MapTask负责一个分片,通过RecordReader来读取每一行数据,但是MmapTask所分配的到的分片不一定是完整的,同一行数据有可能被划分到多个map中。举个例子,一个文件有200M,一行大概100M,只有2条记录。假设我们的splitSize是60M,也就是最终会切割成4个分片:0~59MB60~119MB120~179MB180~200B,所以会启动4个MapTask来处理数据。那这4个task是怎么处理两条数据的呢? 这种情况RecordReader会进行处理,如果LineRecordReader的读取逻辑中,是这样处理的:

  • 第一个分片:发现起始是0,就直接往后读取一整行的数据。也就是这个map task实际处理的数据量是100M,即一条记录;
  • 第二个分片:起始不是0,它就会在通过指定的行分割符去找下一行的起始位置开始读,也就是定位100M的位置,然后开始读取一行的数据。这个map task实际处理的数据量也是100M,即一条记录;
  • 第三个分片:起始不是0,往后扫描也找不到下一个行分割符,所以这个map task处理的数据量是0M;
  • 第四个分片:和分片三一样,处理的数据量是0M。

所以虽然分了4个分片,但实际有处理数据量的task其实就2个,还有两个task实际基本没做什么事,等于浪费了一定的资源

reduce数目确定方法

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

  1. hive.exec.reducers.bytes.per.reducer代表的是每个reduce任务处理的数据量,默认为1G;
  2. hive.exec.reducers.max代表每个任务最大的reduce数,默认为999。

我们来计算下reducer数: N=min(参数2,总输入数据量 / 参数1)。所以如果reduce的输入<map的输出>总大小不超过1G,那么只会有一个reduce任务,我们可以调节参数1来调整Reduce数目;

另外我们还可以强制指定reduce个数set mapred.reduce.tasks = 15

思考

  1. 是不是map越多越好? 如果一个任务有很多小文件<远远小于块大小128m>,则每个小文件也会被当做一个块,用一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费,而且,同时可执行的map数是受限的;

  2. 是不是map越少越好?少了可能每个mapper执行的数据量过大,可能导致内存问题,也会导致处理时间增多;

  3. 是不是reducer越多越好? 同map一样,启动和初始化reduce也会消耗时间和资源;另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

  4. 是不是reducer越少越好?可能会导致内存吃紧。

参考

  1. https://www.iteye.com/blog/superlxw1234-1582880
  2. https://blog.csdn.net/scgaliguodong123_/article/details/45477323
  3. https://www.jianshu.com/p/007ce9991292
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值