6.4.3 优化洗牌(shuffle)和排序阶段

洗牌和排序阶段都很耗费资源。洗牌需要在map和reduce任务之间传输数据,会导致过大的网络消耗。排序和合并操作的消耗也是很显著的。这一节将介绍一系列的技术来缓解洗牌和排序阶段的消耗。

技术46 规避使用reduce

Reduce在用于连接数据集的时候将会产生大量的网络消耗。

问题

需要考虑在MapReduce规避reduce的使用。

方案

通过将MapReduce参数setNumReduceTasks设置为0来创建一个只有map的作业。

讨论

洗牌和排序阶段一般都是用来连接数据集。但连接操作并不一定需要洗牌和排序,正如第4章中所介绍的。满足一定条件的连接可以只在map端运行。那么就只需要只有map的作业了。设置只有map的作业的命令如下。

job.setNumReduceTasks();

小结

一个只有map的作业的OutputFormat是和普通作业中reduce的OutputFormat一样。如图6.39所示。

如果无法规避reduce,那么就要尽量减小它对你的作业执行时间的影响。

技术47 过滤和投影

Map到Reduce之间传输数据要通过网络,这个成本很高。

问题

需要减少被洗牌的数据。

方案

减少map输出的每条记录的大小,并尽可能地减少map输出的数据量。

讨论

过滤和投影是关系运算中的概念,用以减少需要处理的数据。这些概念也可以用到MapReduce中减少map任务需要输出的数据。以下是过滤和投影的简明定义:

  • 过滤是减少map输出的数据量。
  • 投影是减少map输出的每条记录的大小。

以下是上述概念的演示代码:

 Text outputKey = new Text();
Text outputValue = new Text(); @Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output,
Reporter reporter) throws IOException { String v = value.toString(); if (!v.startsWith("10.")) {
String[] parts = StringUtils.split(v, ".", 3);
outputKey.set(parts[0]);
outputValue.set(parts[1]);
output.collect(outputKey, outputValue);
}
}

小结

过滤和投影是在需要显著减少MapReduce作业运行时间时最容易的方法中的两种。

如果已经应用了这两种方法,但还需要进一步减少运行时间。那么就可以考虑combine。

技术48 使用combine

Combine可以在map阶段进行聚合操作来减少需要发送到reduce的数据。它是一个map端的优化工具,以map的输出作为输入。

问题

需要在过滤和投影后进一步减少运行时间。

方案

定义一个combine。在作业代码中使用setCombinerClass来调用它。

讨论

在map输出数据到磁盘的过程中,有两个子过程:溢洒(spill)子过程,合并子过程。Combine在这两个子过程中都会被调用,如图6.40所示。为了让combine在分组数据中效率最大,可以在两个子过程调用combine之前进行初步(precursory)的排序。

与设置map类类似,作业使用setCombinClass来设置combine。

job.setCombinerClass(Combine.class);

Combine的实现必须严格遵从reduce的规格说明。这里将假定使用技术39种的map。将map的输出中的记录按照下述条件合并:第二个八进制数相同。代码如下。

 public static class Combine implements Reducer<Text, Text, Text, Text> {

     @Override
public void reduce(Text key, Iterator<Text> values,
OutputCollector<Text,
Text> output,
Reporter reporter) throws IOException { Text prev = null;
while (values.hasNext()) {
Text t = values.next();
if (!t.equals(prev)) {
output.collect(key, t);
}
prev = ReflectionUtils.copy(job, t, prev);
}
}
}

Combine函数必须是可分布的(distributive)。如图6.40(在前面)所示,combine要被调用多次处理多个具有相同输入键的记录。这些记录的顺序是不可预测的。可分布函数是指,不论输入数据的顺序如何,最终的结果都一样。

小结

在MapReduce中combine非常有用,它能够减少map和reduce之间的网络传输数据和网络负载。下一个减少执行时间的有用工具就是二进制比较器。

技术49 用Comparator进行超快排序

MapReduce默认使用RawComparator对map的输出键进行比较排序。内置的Writable类(例如Text和IntWritable)是字节级实现。这样不用将字节形式的类解排列(unmarshal)成类对象。如果要通过WritableComparable实现自定义Writable,就有可能延长洗牌和排序阶段的时间,因为它需要进行解排列。

问题

存在自定义的Writable。需要减少作业的排序时间。

方案

实现字节级的Comparator来优化排序中的比较过程。

讨论

在MapReduce中很多阶段,排序是通过比较输出键来进行的。为了加快键排序,所有的map输出键必须实现WritableComparable接口。

 public interface WritableComparable<T> extends Writable, Comparable<T> {

 }

如果对4.2.1中的Person类进行改造,实现代码如下。

 public class Person implements WritableComparable<Person> {
private String firstName;
private String lastName; @Override
public int compareTo(Person other) {
int cmp = this.lastName.compareTo(other.lastName);
if (cmp != 0) {
return cmp;
}
return this.firstName.compareTo(other.firstName);
}
...

这个Comparator的问题在于,如果要进行比较,就需要将字节形式的map的中间结果数据解排列成Writable形式。解排列要重新创建对象,因此成本很高。

Hadoop中的自带的各种Writable类不但扩展了WritableComparable接口,也提供了基于WritableComparator类的自定义Comparator。代码如下。

 public class WritableComparator implements RawComparator {

     public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {

         try {
buffer.reset(b1, s1, l1);
key1.readFields(buffer); buffer.reset(b2, s2, l2);
key2.readFields(buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return compare(key1, key2);
} /** Compare two WritableComparables.
*
* <p> The default implementation uses the natural ordering,
* calling {@link
* Comparable#compareTo(Object)}. */
@SuppressWarnings("unchecked")
public int compare(WritableComparable a, WritableComparable b) {
return a.compareTo(b);
}
...
}

要实现字节级的Comparator,需要重载compare方法。这里先学习一下IntWritable类如何实现这个方法。

 public class IntWritable implements WritableComparable {

     public static class Comparator extends WritableComparator {

         public Comparator() {
super(IntWritable.class);
} public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
int thisValue = readInt(b1, s1);
int thatValue = readInt(b2, s2);
return (thisValue<thatValue ? -1 :
(thisValue==thatValue ? 0 : 1));
}
} static {
WritableComparator.define(IntWritable.class, new Comparator());
}

如果只使用内置的Writable,那就没有必要实现WritableComparator。它们都自带。如果需要使用自定义的Writable作为输出键,那么就需要自定义WritableComparator。这里基于前述Person类来说明如何实现。

在Person类中,有两个字符串类属性,firstName和lastName。使用writeUTF方法通过DataOutput输出它们。以下是实现代码。

 private String firstName;
private String lastName; @Override
public void write(DataOutput out) throws IOException {
out.writeUTF(lastName);
out.writeUTF(firstName);
}

首先需要理解Person对象是如何用字节形式表示的。writeUTF方法输出了字节长度(2个字节),字符内容(字符的长度,L1个字节)。如图6.41描述了字节是如何排列的。

假设需要对lastName和firstName进行字典式地比较(译注:就是看字典中的先后顺序)。显然不能直接用整个字节数组,因为其中还有字符长度。那么Comparator就需要足够聪明到能够跳过字符长度。以下是实现代码。

 @Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) { int lastNameResult = compare(b1, s1, b2, s2);
if (lastNameResult != 0) {
return lastNameResult;
}
int b1l1 = readUnsignedShort(b1, s1);
int b2l1 = readUnsignedShort(b2, s2);
return compare(b1, s1 + b1l1 + 2, b2, s2 + b2l1 + 2);
} public static int compare(byte[] b1, int s1, byte[] b2, int s2) {
int b1l1 = readUnsignedShort(b1, s1);
int b2l1 = readUnsignedShort(b2, s2);
return compareBytes(b1, s1 + 2, b1l1, b2, s2 + 2, b2l1);
} public static int readUnsignedShort(byte[] b, int offset) {
int ch1 = b[offset];
int ch2 = b[offset + 1];
return (ch1 << 8) + (ch2);
}

小结

writeUTF只支持小于65536字符的字符串类。对于人名来说,是足够了。大点的,可能就不行。这个时候就需要使用Hadoop的Text类来支持更大的字符串。Text类中的Comparator类的二进制字符串比较器的实现机制和刚才介绍的大致相当。(这个修饰真长。)那么针对Text类的lastName和firstName的Comparator的实现方式也会累死。

下一节将介绍如何减小数据倾斜的影响。

[大牛翻译系列]Hadoop(13)MapReduce 性能调优:优化洗牌(shuffle)和排序阶段的更多相关文章

  1. [大牛翻译系列]Hadoop 翻译文章索引

    原书章节 原书章节题目 翻译文章序号 翻译文章题目 链接 4.1 Joining Hadoop(1) MapReduce 连接:重分区连接(Repartition join) http://www.c ...

  2. 【Xamarin挖墙脚系列:应用的性能调优】

    原文:[Xamarin挖墙脚系列:应用的性能调优] 官方提供的工具:网盘地址:http://pan.baidu.com/s/1pKgrsrp 官方下载地址:https://download.xamar ...

  3. [大牛翻译系列]Hadoop(16)MapReduce 性能调优:优化数据序列化

    6.4.6 优化数据序列化 如何存储和传输数据对性能有很大的影响.在这部分将介绍数据序列化的最佳实践,从Hadoop中榨出最大的性能. 压缩压缩是Hadoop优化的重要部分.通过压缩可以减少作业输出数 ...

  4. [大牛翻译系列]Hadoop(8)MapReduce 性能调优:性能测量(Measuring)

    6.1 测量MapReduce和环境的性能指标 性能调优的基础系统的性能指标和实验数据.依据这些指标和数据,才能找到系统的性能瓶颈.性能指标和实验数据要通过一系列的工具和过程才能得到. 这部分里,将介 ...

  5. [大牛翻译系列]Hadoop(15)MapReduce 性能调优:优化MapReduce的用户JAVA代码

    6.4.5 优化MapReduce用户JAVA代码 MapReduce执行代码的方式和普通JAVA应用不同.这是由于MapReduce框架为了能够高效地处理海量数据,需要成百万次调用map和reduc ...

  6. [大牛翻译系列]Hadoop(11)MapReduce 性能调优:诊断一般性能瓶颈

    6.2.4 任务一般性能问题 这部分将介绍那些对map和reduce任务都有影响的性能问题. 技术37 作业竞争和调度器限制 即便map任务和reduce任务都进行了调优,但整个作业仍然会因为环境原因 ...

  7. [大牛翻译系列]Hadoop(10)MapReduce 性能调优:诊断reduce性能瓶颈

    6.2.3 Reduce的性能问题 Reduce的性能问题有和map类似的方面,也有和map不同的方面.图6.13是reduce任务的具体的执行各阶段,标识了可能影响性能的区域. 这一章将介绍影响re ...

  8. [大牛翻译系列]Hadoop(3)MapReduce 连接:半连接(Semi-join)

    4.1.3 半连接(Semi-join) 假设一个场景,需要连接两个很大的数据集,例如,用户日志和OLTP的用户数据.任何一个数据集都不是足够小到可以缓存在map作业的内存中.这样看来,似乎就不能使用 ...

  9. [大牛翻译系列]Hadoop(1)MapReduce 连接:重分区连接(Repartition join)

    4.1 连接(Join) 连接是关系运算,可以用于合并关系(relation).对于数据库中的表连接操作,可能已经广为人知了.在MapReduce中,连接可以用于合并两个或多个数据集.例如,用户基本信 ...

随机推荐

  1. JavaScript 之 执行前台函数

    1.OnClientClick (vs2003不支持这个方法) <asp:Button ID="Button" runat="server" Text=& ...

  2. linux安装问题

    java: cannot execute binary file问题 主要原因是 linux系统是32位的,jdk版本是64位的. 补充知识: 1.查看linux位数: #uname -a 如果有x8 ...

  3. SQL查询中的in与join效率比较

    大多数情况下,程序员比较喜欢使用in来查询符合某些条件的数据,最近在查询某个角色有哪些用户的方法中,使用了in语句: ) FROM baseuser AND BaseUser.Id IN (SELEC ...

  4. Oracle基础 存储过程和游标

    一.带游标的存储过程 游标作为参数有两种类型: 1.声明系统游标类型 SYS_REFCURSOR 1)游标作为存储过程的参数: --带游标的存储过程 CREATE OR REPLACE PROCEDU ...

  5. Oracle 约束类型

    在Oracle中的约束类型:NOT NULLUNIQUE KeyPRIMARY KEYFOREIGN KEYCHECK create table emp--创建表格 ,注意约束( empno numb ...

  6. django 学习-16 Django会话Cookie

    1.django.admin.py  startproject   cs3 cd cs3 django.admin.py   startapp   blog 2.    vim urls.py url ...

  7. 【转载】看懂SqlServer查询计划

    看懂SqlServer查询计划 阅读目录 开始 SQL Server 查找记录的方法 SQL Server Join 方式 更具体执行过程 索引统计信息:查询计划的选择依据 优化视图查询 推荐阅读-M ...

  8. Android之adb命令

    1.安装APK(如果加 -r 参数,保留已设定数据,重新安装filename.apk) adb install xxx.apk adb install -r xxx.apk 2.卸载APK(如果加 - ...

  9. ef codeFirst 修改表结构 增加字段等 EF code first需要重新生成库导致数据丢失的问题.

    需要在库程序包管理器里依次执行以下命令: 1.启用迁移功能:Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDbContext 2.建立 ...

  10. iOS中关于.pch的新建与配置问题

    以前版本的Xcode新建一个项目都会自动生成.pch,这个文件的好处是,里面添加的东西会自动添加到每个类中,也就是说我们可以把要用的宏定义,和多个头文件等放到.pch中,这样我们就不需要重复的在每个类 ...