Hadoop调优经验

hadoop一般会有两种使用模式:一种是大部分场景所使用的,进行大规模的数据统计和分析。例如很多公司每天都需要做的分析日志,计算PV,UV 等等。第二种则是用于实现大规模数据处理的算法,例如实现协同过滤算法。两种模式存在着一些使用上的差异:前者业务逻辑简单,代码的实现也比较容易,目前大部分工作都可以通过Hive直接操作,不需要再手写Mapper和Reducer,此外,在计算过程中,MapTask的数量会远大于 ReduceTask的数量;后一种模式,代码的实现更为复杂,Hive或者Streaming方式可能无法胜任,必须要写原生的Mapper和 Reducer。

我自己更多的是在从事后一种模式的工作,时间长了有些经验:
1. 内存的使用很重要。很多算法的Mapper和Reducer内部的逻辑会比较复杂,而且经常要预先加载一些数据,对内存的使用很敏感。加载的数据量稍微大一点,就可能导致java.lang.OutOfMemoryError: Java heap space的错误(当然,我使用的Hadoop集群计算资源有限也是一个客观的原因)。
以前常常认为有了Hadoop或者其它类似的分布式系统之后,大数据量的计算我们不太需要关注这些细节的问题,但其实不对。相对于数据量和计算任务,计算资源永远都是短缺的。而在“Big Data”这个大环境下,我认为内存的使用尤其重要,应该尽可能加载更多的数据到内存,应该尽可能重复使用加载到内存中的数据;

2. 调节Hadoop运行参数很有用。Hadoop有很多的参数可以设置。了解这些参数,然后根据业务需求设置合理的值,能够让计算时间压缩几倍甚至十几倍,同时磁盘空间的使用也可以缩减不少;

3. Hadoop确实只适用于部分模式的算法。MapReduce模型只能够满足一部分的算法计算,适用范围有限。比如用Hadoop去实现图的SSSP算法就非常的坑爹;

下面是我总结的一些Hadoop调优有效的手段。坦率的说,写完之后发现其实和网上大部分文章所提及的大同小异。之所以还是写了下来,是因为这些已经变成了自己的东西。

尽量使用Combiner
        job.setCombinerClass(MyReducer.class);

使用Combiner的本质,是为了减少MapReduce过程的中间数据,即Mapper的输出也就是Reduce的输入。因为中间数据最终是要写入到硬盘上的,所以减少数据的量,意味着写硬盘的时间会降低。此外,Map和Reduce之间其实还有一个称为shuffle的过程,包括一系列数据的拷贝和排序操作(拷贝是跨机器的,需要占用网络带宽),使用Combiner能够在shuffle之前就将数据进行整合,大大减少shuffle需要操作的数据量。(关于shuffle的具体步骤,这里说得很清楚)
所以,大部分情况下,尽可能的使用Combiner。我只碰到过一次例外:我的Combiner和Reducer都需要加载大数据,很占内存。而Combiner实际上是在Mapper中完成的,因为Mapper很多,导致内存不够用程序崩溃。这个情况下,我只能忍痛不采用Combiner,事实证明,速度变慢很多。

对中间结果进行压缩
        conf.set(“mapred.compress.map.output”, “true”);

对中间结果进行压缩,减少中间结果的数据量,原因其实前面已经解释了。损失一些CPU,换来的是磁盘和网络IO。只要数据量比较大,设置这个参数一定能够提高运行速度,如果数据量不太大,设置与否影响不大,所以简单的方法就是永远将mapred.compress.map.output参数设置为true。

调节Mapper和Reducer的数量

Mapper和Reducer的数量代表的是计算的并行程度。理论上,Mapper和Reducer数量越多,计算的并行化越高,完成任务越快。但实际中,一方面,整个Hadoop池不一定能够容纳下足够多的Mapper和Reducer,另一方面,Mapper和Reducer本身也会消耗一定的资源,所以,如果每个Mapper只运行了很短的时间就完成,其实是一种资源的浪费。

配置dfs.block.size

Mapper的数量是无法通过在程序中设置参数进行调节的。dfs.block.size是在全局配置文件hdf-site.xml中设置的。这个参数决定了每个block的大小。而Mapper的数量和Input数据的block的数量是一一对应的关系。所以,可以通过配置dfs.block.size参数来调节Mapper的数量。将参数设置高一些,则Input数据的block数量就会变少,Mapper的数量也就变少。

job.setNumReduceTasks(num);

程序中调用setNumReduceTasks方法可以直接设置Reducer的数量。

调节Java Heap Size
       conf.set(“mapred.child.java.opts”, “-Xmx1g”);

修改Java Heap Size的值。
我们应该提高尽可能Heap Size的值。一方面,前文提到,很多时候我们要预先加载一些数据到Mapper或者Reducer中进行计算,这就要求HeapSize足够的大,另一方面,即使不需要预加载很多的数据,更大的HeapSize可以使用更大的Buffer,将更多的数据先缓存在内存中,减少硬盘IO的次数。所以,如果你的计算比较耗内存,或者你有足够的内存可用,那么提高Heap Size是一个简单但又非常有效的手段。
但是,Heap Size并不是越大越好。如果计算中会产生很多的MapTask或者ReduceTask,而每个Task都占用很大的HeapSize,那么可能导致所有的内存被消耗光,甚至不够用。所以,在设置Heap Size的时候必须考虑到Task可能的数量以及整体计算资源的多寡。

重复的使用Writable对象

这是一个编程习惯的问题,目的也是为了有效利用内存,减少GC。有的时候,Mapper或者Reducer会写多条记录,例如:

   1:  for (String word : words) {
   2:      ctx.write(new Text(word), new IntWritable(1));
   3:  }

每次写数据,都会创建一个临时的Text和IntWritable对象。更好的做法是:

   1:  Text wordText = new Text();
   2:  IntWritable one = new IntWritable(1);
   3:  for (String word : words) {
   4:      wordText.set(word);
   5:      ctx.write(wordText, one);
   6:  }

这样可以避免构造很多的临时对象。
其实不仅仅是writable,所有的地方都应该减少这种大量临时变量的构造。

对Mapper和Reducer的调节
相关细节和原理,这里(“hadoop作业调优参数整理及原理”)写的更清楚。
我经常设置的参数有 io.sort.factor 和 io.sort.mb 这两个。

一些好资料:
http://www.searchtb.com/2010/12/hadoop-job-tuning.html
http://www.slideshare.net/ImpetusInfo/ppt-on-advanced-hadoop-tuning-n-optimisation
http://www.cloudera.com/blog/2009/12/7-tips-for-improving-mapreduce-performance/

—END—

Advertisements
相册 | 此条目发表在Hadoop分类目录,贴了标签。将固定链接加入收藏夹。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s