Distributed System: MapReduce 可调参数与优化方向

参考前一篇文章:http://blog.csdn.net/firehotest/article/details/69498843


我们可以知道,MapReduce的详细流程如下:


将InputFile进行Split之后,每个Split的块对应一个Mapper。默认的块大小和Block大小一致,64MB。然后通过Record Reader进行字节流到初始KV对的转换。譬如WordCount的话,这时候的初始KV对是“行号 - 内容”。


接着Mapper根据计算目标对初始KV对进行转换(利用用户自定义的map方法的逻辑),譬如WordCount的话转换出的KV对是“Word - 1”。Mapper的输出会送到本地的机子的heap里面进行Shuffle and Sort。这时候可以用用户自定义的Partitioner进行分组或者用框架默认的分组,但无论哪一种,分组的组数都必须和Reducer的数目一致。在送往Reducer Copy之前,可以自己设置一个Combiner进行Local Aggregation. 这样就可以减少Reducer的Copy阶段的网络延迟(减少传输量)。


到了Reducer,分为Copy, Merge和Reduce三个阶段。一般,在第一个Mapper完成了后,Reducer就开始工作了。首先是,Copy阶段,从每个Mapper的输出复制属于自己的KV对,这个阶段是耗时最严重的。其次是Merge阶段,把缓冲在内存的KV对写进硬盘。最后就是真正执行reduce方法的阶段。


了解了上述的过程后,下面我们来说说可调(可优化的参数有哪些):


1/ 在mapper的数量和split方面(split决定了mapper的数量)


map正常的并行规模大致是每个节点(node)大约10到100个map,对于CPU 消耗较小的map任务可以设到300个左右。


因为启动任务也需要时间,所以在一个较大的作业中,最好每个map任务的执行时间不要少于1分钟,这样可以让启动任务的开销占比尽可能的低。


1) 对于那种有大量小文件输入的的作业来说,一个map处理多个文件会更有效率。
2) 如果输入的是大文件,那么一种提高效率的方式是增加block的大小(比如512M),每个map还是处理一个完整的HDFS的block。对于Mapper的输出需要Shuffle和Sort,这个步骤需要用到内存。所以,当在map处理的block比较大的时候,确保有足够的内存作为排序缓冲区是非常重要的,这可以加速map端的排序过程。假如大多数的map输出都能在排序缓冲区中处理的话应用的性能会有极大的提升。这需要运行map过程的JVM具有更大的堆。


2/ 在Partitioner (分区)的自定义方面


对于哪一组分给哪个reducer, 用户可以通过实现自定义的 Partitioner来控制哪个key被分配给哪个 Reducer。


在把map()输出数据写入内存缓冲区之前会先进行Partitioner操作。(也就是说在Shuffle and Sort之前)Partitioner用于划分键值空间(key space)。MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认对key hash后再以reduce task数量取模。(默认的Partitioner 是 取模的哈希算法)默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。


reducer=(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks

HashPartitioner是默认的 Partitioner。


3/ 在Combiner的开启和定义方面


对于map的输出,用户可选择通过JobConf.setCombinerClass(Class)指定一个combiner,它负责对中间过程的输出进行本地的聚集,这会有助于降低从Mapper到 Reducer数据传输量。


Combiner最主要的好处在于减少了shuffle过程从map端到reduce端的传输数据量。但是并非是所有情况都适合用Combiner,因为它需要一次额外的对于map输出的序列化/反序列化过程。不能通过Combination将map端的输出减少到20-30%的话就不适用combiner。Combiner其实也是一种reduce操作。Combiner是一个本地化的reduce操作,它是map运算的后续操作,主要是在map计算出中间文件前做一个简单的合并重复key值的操作。


Combiner操作是有风险的,使用它的原则是combiner的输入不会影响到reduce计算的最终输入,例如:如果计算只是求总数,最大值,最小值可以使用combiner,但是做平均值计算使用combiner的话,最终的reduce计算结果就会出错。


4/ 在定义Mapper的输出文件的压缩方面


这些被排好序的中间过程的输出结果保存的格式是(key-len, key, value-len, value),应用程序可以通过JobConf控制对这些中间结果是否进行压缩以及怎么压缩,使用哪种CompressionCodec。


Map/Reduce框架为应用程序的写入文件操作提供压缩工具,这些工具可以为map输出的中间数据和作业最终输出数据(例如reduce的输出)提供支持。意思就是说,不仅可以压缩中间的结果,还可以压缩Reducer的输出结果。


压缩中间数据: 对map输出的中间数据进行合适的压缩可以减少map到reduce之间的网络数据传输量,从而提高性能。Lzo压缩格式是一个压缩map中间数据的合理选择,它有效利用了CPU。


压缩应用输出: 使用合适的压缩格式压缩输出数据能够减少应用的运行时间。Zlib/Gzip 格式在大多数情况下都是比较适当的选择,因为它在较高压缩率的情况下压缩速度也还算可以,bzip2 就慢得多了。


5/ 在Reducer的Copy阶段


Reduce进程启动数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。由于map通常有许多个,所以对一个reduce来说,下载也可以是并行的从多个map下载,这个并行度是可以通过mapred.reduce.parallel.copies(default 5)调整。默认情况下,每个只会有5个并行的下载线程在从map下数据,如果一个时间段内job完成的map有100个或者更多,那么reduce也最多只能同时下载5个map的数据,所以这个参数比较适合map很多并且完成的比较快的job的情况下调大,有利于reduce更快的获取属于自己部分的数据。


reducer的每一个下载线程在下载某个map数据的时候,有可能因为那个map中间结果所在机器发生错误,或者中间结果的文件丢失,或者网络瞬断等等情况,这样reducer的下载就有可能失败,所以reducer的下载线程并不会无休止的等待下去,当一定时间后下载仍然失败,那么下载线程就会放弃这次下载,并在随后尝试从另外的地方下载(因为这段时间map可能重跑)。reducer下载线程的这个最大的下载时间段是可以通过mapred.reduce.copy.backoff(default 300秒)调整的。如果集群环境的网络本身是瓶颈,那么用户可以通过调大这个参数来避免reduce下载线程被误判为失败的情况。不过在网络环境比较好的情况下,没有必要调整。通常来说专业的集群网络不应该有太大问题,所以这个参数需要调整的情况不多。


6/ 在Reducer的Merge阶段


Reducer将Copy过来的KV都会先存在缓冲区,等满了之后再写入文件(磁盘)。


这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置。这个内存大小的控制就不像map一样可以通过io.sort.mb来设定了,而是通过另外一个参数 mapred.job.shuffle.input.buffer.percent(default 0.7) 来设置, 这个参数其实是一个百分比,意思是说,shuffile在reduce内存中的数据最多使用内存量为:0.7 × maxHeap of reduce task。


也就是说,如果该reduce task的最大heap使用量(通常通过mapred.child.java.opts来设置,比如设置为-Xmx1024m)的一定比例用来缓存数据。默认情况下,reduce会使用其heapsize的70%来在内存中缓存数据。假设 mapred.job.shuffle.input.buffer.percent 为0.7,reduce task的max heapsize为1G,那么用来做下载数据缓存的内存就为大概700MB左右。这700M的内存,跟map端一样,也不是要等到全部写满才会往磁盘刷的,而是当这700M中被使用到了一定的限度(通常是一个百分比),就会开始往磁盘刷(刷磁盘前会先做sort)。这个限度阈值也是可以通过参数 mapred.job.shuffle.merge.percent(default 0.66)来设定。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。这种merge方式一直在运行,直到没有map端的数据时才结束,然后启动磁盘到磁盘的merge方式生成最终的那个文件。


有一个参数也是可以调整reduce的计算行为。也就是mapred.job.reduce.input.buffer.percent(default 0.0)。由于reduce计算时肯定也是需要消耗内存的,而在读取reduce需要的数据时,同样是需要内存作为buffer,这个参数是控制,需要多少的内存百分比来作为reduce读已经sort好的数据的buffer百分比。默认情况下为0,也就是说,默认情况下,reduce是全部从磁盘开始读处理数据。如果这个参数大于0,那么就会有一定量的数据被缓存在内存并输送给reduce,当reduce计算逻辑消耗内存很小时,可以分一部分内存用来缓存数据,反正reduce的内存闲着也是闲着。


7/ 设置Reducer的数量


Reduce的数目建议是0.95或1.75乘以 ( * mapred.tasktracker.reduce.tasks.maximum)。 用0.95,所有reduce可以在maps一完成时就立刻启动,开始传输map的输出结果。用1.75,速度快的节点可以在完成第一轮reduce任务后,可以开始第二轮,这样可以得到比较好的负载均衡的效果。


reduces的性能很大程度上受shuffle的性能所影响。应用配置的reduces数量是一个决定性的因素。太多或者太少的reduce都不利于发挥最佳性能: 太少的reduce会使得reduce运行的节点处于过度负载状态,在极端情况下我们见过一个reduce要处理100g的数据。这对于失败恢复有着非常致命的负面影响,因为失败的reduce对作业的影响非常大。太多的reduce对shuffle过程有不利影响。在极端情况下会导致作业的输出都是些小文件,这对NameNode不利,并且会影响接下来要处理这些小文件的mapreduce应用的性能。在大多数情况下,应用应该保证每个reduce处理1-2g数据,最多5-10g。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值