MapReduce优化: Combiner和Partitioner

       在hadoop Mapreduce优化技术中,总会涉及到Combiner和Partitioner,Combiner和Partitioner是用来优化MapReduce的。可以提高MapReduce的运行效率,下面就来谈谈这两种技术及其简单的使用。

1 Combiner技术

Combiner是一个本地化的reduce操作,它是map运算的后续操作,主要是在map计算出中间文件前做一个简单的合并重复key值的操作。集群上的可用带宽限制了MapReduce作业的数量,因此尽量避免map和reduce任务之间的数据传输是有利的。Hadoop允许用户针对map任务的输出指定一个combiner(就像mapper,reducer)。combiner函数的输出作为reduce函数的输入。由于combiner术语优化方案,所以Hadoop无法确定对map任务输出记录调用多少次combiner(如果需要)。换言之,不管调用多次combiner,reducer的输出结果都是一样的。

首先通过下面的示意图直观的了解一下Combiner的位置和作用。 
从下图可以看出,Combiner介于 Mapper和Reducer之间,combine作为 Map任务的一部分,执行完 map 函数后紧接着执行combine,而reduce 必须在所有的 Map 任务完成后才能进行。 而且还可以看出combine的过程与reduce的过程类似,都是对相同的单词key合并其词频,很多情况下可以直接使用reduce函数来完成Combiner过程。

è¿éåå¾çæè¿°

1.1 Combiner解析: 
1. ombiner可以看做局部的Reducer(local reducer)。 
2. Combiner作用是合并相同的key对应的value。 
3. 在Mapper阶段,不管Combiner被调用多少次,都不应改变 Reduce的输出结果。 
4. Combiner通常与Reducer的逻辑是一样的,一般情况下不需要单独编写Combiner,直接使用Reducer的实现就可以了。 
5. Combiner在Job中是如下设置的。

 job.setCombinerClass(Reducer.class);//Combiner一般情况下,默认使用Reducer的实现

1.2 Combiner的优点

能够减少Map Task输出的数据量(即磁盘IO)。对spill,merge文件都可以进行压缩。 
中间结果非常大导致IO成为瓶颈时压缩非常有用,可以通过mapreduce.map.output.compress(default:false)设置为true进行压缩,数据会被压缩写入磁盘,读数据读的是压缩数据需要解压,在实际经验中Hive在Hadoop的运行的瓶颈一般都是IO而不是CPU,压缩一般可以10倍的减少IO操作,压缩的方式Gzip,Lzo,BZip2,Lzma等,其中Lzo是一种比较平衡选择,mapreduce.map.output.compress.codec(default:org.apache.hadoop.io.compress.DefaultCodec)参数设置。但这个过程会消耗CPU,适合IO瓶颈比较大。
能够减少Reduce-Map网络传输的数据量(网络IO)。Map Task 输出越少,Reduce从Map结果中拉取的数据量就越少,自然就减少了网络传输的数据量。


1.3 Combiner的使用场景

并不是所有的场景都可以使用Combiner,必须满足结果可以累加。适合于Sum()求和,并不适合Average()求平均数。 
例如,求0、20、10、25和15的平均数,直接使用Reduce求平均数Average(0,20,10,25,15),得到的结果是14, 如果先使用Combiner分别对不同Mapper结果求平均数,Average(0,20,10)=10,Average(25,15)=20,再使用Reducer求平均数Average(10,20),得到的结果为15,很明显求平均数并不适合使用Combiner。

 

2 Partitioner

       Partitioner 处于 Mapper阶段,当Mapper处理好数据后,这些数据需要经过Partitioner进行分区,来选择不同的Reducer处理,从而将Mapper的输出结果均匀的分布在Reducer上面执行。

对于map输出的每一个键值对,系统都会给定一个partition,partition值默认通过计算key的hash值后对Reduce task的数量取模获得。如果一个键值对的partition值为1,意味着这个键值对会交给第一个Reducer处理。

è¿éåå¾çæè¿°

Partitioner解析:

Partitioner决定了Map Task 输出的每条数据交给哪个Reduce Task 来处理。Partitioner 有两个功能: 
1) 均衡负载。它尽量将工作均匀地分配给不同的 Reduce。 
2)效率。它的分配速度一定要非常快。

Partitioner 的默认实现:hash(key) mod R,这里的R代表Reduce Task 的数目,意思就是对key进行hash处理然后取模。很多情况下,用户需要自定义 Partitioner,比如“hash(hostname(URL)) mod R”,它确保相同域名下的网页交给同一个 Reduce Task 来处理。 用户自定义Partitioner,需要继承Partitioner类,实现它提供的一个方法。
 

public class WordCountPartitioner extends Partitioner<Text, IntWritable> {
    @Override
    public int getPartition(Text key, IntWritable value, int numPartitions) {
        // TODO Auto-generated method stub
        int a = key.hashCode()%numPartitions;
        if(a>=0)
            return a;
        else 
            return 0;
    }
}

前两个参数分别为Map的key和value。args 为 Reduce 的个数,用户可以自己设置。

如何自定义partitioner?

每一个Reduce的输出都是有序的,但是将所有Reduce的输出合并到一起却并非是全局有序的,如果要做到全局有序,我们该怎么做呢?最简单的方式,只设置一个Reduce task,但是这样完全发挥不出集群的优势,而且能应对的数据量也很受限。最佳的方式是自己定义一个Partitioner,用输入数据的最大值除以系统Reduce task数量的商作为分割边界,也就是说分割数据的边界为此商的1倍、2倍至numPartitions-1倍,这样就能保证执行partition后的数据是整体有序的。

解决数据倾斜:另一种需要我们自己定义一个Partitioner的情况是各个Reduce task处理的键值对数量极不平衡。对于某些数据集,由于很多不同的key的hash值都一样,导致这些键值对都被分给同一个Reducer处理,而其他的Reducer处理的键值对很少,从而拖延整个任务的进度。当然,编写自己的Partitioner必须要保证具有相同key值的键值对分发到同一个Reducer。

自定义的Key包含了好几个字段,比如自定义key是一个对象,包括type1,type2,type3,只需要根据type1去分发数据,其他字段用作二次排序。
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值