[大牛翻译系列]Hadoop(14)MapReduce 性能调优:减小数据倾斜的性能损失
6.4.4 减小数据倾斜的性能损失
数据倾斜是数据中的常见情况。数据中不可避免地会出现离群值(outlier),并导致数据倾斜。这些离群值会显著地拖慢MapReduce的执行。常见的数据倾斜有以下几类:
- 数据频率倾斜——某一个区域的数据量要远远大于其他区域。
- 数据大小倾斜——部分记录的大小远远大于平均值。
在map端和reduce端都有可能发生数据倾斜。在map端的数据倾斜会让多样化的数据集的处理效率更低。在reduce端的数据倾斜常常来源于MapReduce的默认分区器。
数据倾斜会导致map和reduce的任务执行时间大为延长,也会让需要缓存数据集的操作消耗更多的内存资源。
前面6.2.2和6.2.3中的技术29和34介绍了如何诊断是否存在数据倾斜。这一节中将介绍如何确定倾斜的数据,如何缓解它们的影响。
技术50 收集倾斜数据
本技术将关注由map的输出数据中的数据频率倾斜的问题。
问题
需要诊断map输出中哪些键存在数据倾斜。
方案
在reduce方法中加入记录map输出键的详细情况的功能。
讨论
在发现了倾斜数据的存在之后,就很有必要诊断造成数据倾斜的那些键。有一个简便方法就是在代码里实现追踪每个键的最大值。为了减少追踪量,可以设置数据量阀值,只追踪那些数据量大于阀值的键,并输出到日志中。实现代码如下。
1 public static final String MAX_VALUES = "skew.maxvalues"; 2 private int maxValueThreshold; 3 4 @Override 5 public void configure(JobConf job) { 6 maxValueThreshold = job.getInt(MAX_VALUES, 100); 7 } 8 9 @Override 10 public void reduce(Text key, Iterator<Text> values, 11 OutputCollector<Text, Text> output, 12 Reporter reporter) throws IOException { 13 14 int i = 0; 15 16 while (values.hasNext()) { 17 values.next(); 18 i++; 19 } 20 21 if (++i > maxValueThreshold) { 22 log.info("Received " + i + " values for key " + key); 23 } 24 }
运行作业后就可以从日志中判断发生倾斜的键以及倾斜程度。
小结
跟踪倾斜数据是了解数据的重要一步,也是设计MapReduce作业的重要基础。
下一步介绍如何减小Reduce数据倾斜的性能损失了。
技术51 减小Reduce端数据倾斜的性能损失
Reduce数据倾斜一般是指map的输出数据中存在数据频率倾斜的状况,也就是部分输出键的数据量远远大于其它的输出键。
问题
需要减小reduce端数据倾斜的性能损失。
讨论
用一系列的方法减小数据倾斜的风险,例如使用自定义的分区器,使用map端连接等。
方案
在这个方案中将讨论多个减轻reduce数据倾斜的性能损失的方法。
方法1:抽样和范围分区
Hadoop默认的分区器是基于map输出键的哈希值分区。这仅在数据分布比较均匀时比较好。在有数据倾斜时就很有问题。
使用分区器需要首先了解数据的特性。在第4章的TotalOrderPartitioner中,可以通过对原始数据进行抽样得到的结果集来预设分区边界值。TotalOrderPartitioner中的范围分区器可以通过预设的分区边界值进行分区。因此它也可以很好地用在矫正数据中的部分键的数据倾斜问题。
方法2:自定义分区
另一个抽样和范围分区的替代方案是基于输出键的背景知识进行自定义分区。例如,如果map输出键的单词来源于一本书。其中大部分必然是省略词(stopword)。那么就可以将自定义分区将这部分省略词发送给固定的一部分reduce实例。而将其他的都发送给剩余的reduce实例。
方法3:Combine
使用Combine可以大量地减小数据频率倾斜和数据大小倾斜。在可能的情况下,combine的目的就是聚合并精简数据。在技术48种介绍了combine。
方法4:Map端连接和半连接
如果连接的数据集太大而不能在map端的连接中使用。那么可以考虑第4章和第7章中介绍的超大数据集的连接优化方案。
方法5:数据大小倾斜的自定义策略
在map端或reduce端的数据大小倾斜都会对缓存造成较大的影响,乃至导致OutOfMemoryError异常。处理这种情况并不容易。可以参考以下方法。
- 设置mapred.linerecordreader.maxlength来限制RecordReader读取的最大长度。RecordReader在TextInputFormat和KeyValueTextInputFormat类中使用。默认长度没有上限。
- 通过org.apache.hadoop.contrib.utils.join设置缓存的数据集的记录数上限。在reduce中默认的缓存记录数上限是100条。
- 考虑使用有损数据结构压缩数据,如Bloom过滤器。这将在第7章介绍。
小结
下一部分将介绍如何用户代码中对性能影响较大的情况,以及相应的优化方案。