当MapReduce模型中,reduce执行的任务为统计分类类型的值总量或去重后的数量,或最大值最小值时,可以考虑在Map输出后进行combine操作;这样可以减少网络传输带来的开销,同时减轻了reduce任务的负担。
Combine操作是运行在每个节点上的,只会影响本地Map的输出结果;Combine的输入为本地map的输出结果(一般是数据在溢出到磁盘之前,可以减少IO开销),其输出则作为reduce的输入。
很多时候combine的逻辑和reduce的逻辑是相同的,因此两者可以共用Reducer体;这个时候只需要在客户端中设置Map类之后,Reduce类之前加入一行代码: job.setCombinerClass(MyReducer.class);。如果需要自定义combiner类,可以类似这样(示例为从HBase表读取数据,计算,然后写入另一个HBase表中):
public class Test {
public static class MyMapper extends TableMapper<ImmutableBytesWritable, ImmutableBytesWritable> {
@Override
public void map(ImmutableBytesWritable ibw, Result result, Context context) throws IOException, InterruptedException {
// 从HBase中读取数据后,进行map相关操作
// ...
// 将map结果输出到缓冲区
context.write(new ImmutableBytesWritable(myMapKey, new ImmutableBytesWritable(myMapValue));
}
}
public static class MyCombiner extends TableReducer<ImmutableBytesWritable, ImmutableBytesWritable, ImmutableBytesWritable>{
@Override
public void reduce(ImmutableBytesWritable mapKey, Iterable<ImmutableBytesWritable> mapValues, Context context) throws IOException, InterruptedException {
// 一些操作,如distinct, sum, max, distinct
// ...
// 将combine结果输出到缓冲区
context.write(mapKey, new ImmutableBytesWritable(combineValue));
}
}
public static class MyReducer extends TableReducer<ImmutableBytesWritable, ImmutableBytesWritable, ImmutableBytesWritable> {
@Override
public void reduce(ImmutableBytesWritable reduceKeyIBW, Iterable<ImmutableBytesWritable> reduceValues, Context context) throws IOException, InterruptedException {
// 一些操作,如distinct, sum, max, distinct
// ...
// 将reduce结果(类型Put)输出到缓冲区,用于插入到指定HBase表中
context.write(null, put);
}
}
public static void main(String[] args) throws Exception {
Configuration configuration = HBaseConfiguration.create();
Job job = new Job(configuration, "test_mr");
job.setJarByClass(Test.class);
job.setNumReduceTasks(4); // reduce任务数量默认为1
Scan scan = new Scan();
scan.setCacheBlocks(false);
scan.setCaching(1000); //每次从服务器端读取的行数,默认为配置文件中设置的值
TableMapReduceUtil.initTableMapperJob("table_for_read", scan, MyMapper.class, ImmutableBytesWritable.class, ImmutableBytesWritable.class, job);
job.setCombinerClass(MyCombiner.class); // 设置combiner
TableMapReduceUtil.initTableReducerJob("table_to_write", MyReducer.class, job);
job.waitForCompletion(true);
}
}
当combine操作适用而且Map的输出结果数量很大时,combine的作用是很明显的。
以下是某次测试环境中使用combine前后的对比效果,可以看到Combine output records接近Combine input records的三分之一, 而reduce的输入规模接近原来的十分之一。
两次实际的测试用时分别为50mins16sec, 30mins6sec,耗时节省了接近一半。如图所示: