单一MapReduce对一些非常简单的问题提供了很好的支持。但是如果处理过程变得更加复杂,这种复杂性应该体现为更多地MapReduce工作,而不是更加复杂的map函数和reduce函数。
在hadoop 中一个Job中可以按顺序运行多个mapper对数据进行前期的处理,再进行reduce,经reduce后的结果可经个经多个按顺序执行的mapper进行后期的处理,这样的Job是不会保存中间结果的,并大大减少了I/O操作。
例如:在一个Job中,按顺序执行 Map1->Map2->Reduce->Map3->Map4 ,在这种链式结构中,要将Map2与Reduce看成这个MapReduce的核心部分,partitioning与shuffling(奇迹发生的地方)在此处才会被应用到。所以Map1作为前期处理,而Map3与Map4作为后期处理。
代码示例:
Configuration conf = getConf(); JobConf job = new JobConf(conf); job.setJobName(“ChainJob”); job.setInputFormat(TextInputFormat.class); job.setOutputFormat(TextOutputFormat.class); FileInputFormat.setInputPaths(job, in); FileOutputFormat.setOutputPath(job, out); JobConf map1Conf = new JobConf(false); //将map1加入到Job中 ChainMapper.addMapp(job, Map1.class, LongWritable.class, Text.class, Text.class, Text.class, true, map1Conf); //将map2加入到Job中 JobConf map2Conf = new JobConf(false); ChainMapper.addMapper(job, Map.class, Text.class, Text.class, LongWritable.class, Text.class, true, map2Conf); //将Reduce加入到Job中 JobConf reduceConf = new JobConf(false); ChainReducer.setReducer(job, Reduce.class, LongWritable.class, Text.class, Text.class, Text.class, true, reduceConf); //将map3加入到Job中 JobConf map3Conf = new JobConf(false); ChainReducer.addMapper(job, Map3.class, Text.class, Text.class, LongWritable.class, Text.class, true, map3Conf); //将map4加入到Job中 JobConf map4Conf = new JobConf(false); ChainReducer.addMapper(job, Map4.class, LongWritable.class, Text.class, LongWritable.class, Text.class, true, map4Conf); JobClient.runJob(job);
addMapper方法的签名(setReducer方法与此类似)
public static <K1,V1,K2,V2> void
addMapper(JobConf job,
Class<? extends Mapper<K1,V1,K2,V2>> klass,
Class<? extends K1> inputKeyClass,
Class<? extends V1> inputValueClass,
Class<? extends K2> outputKeyClass,
Class<? extends V2> outputValueClass,
boolean byValue,
JobConf mapperConf)
关于byValue参数:
在标准的Mapper模型中,键/值对的输出在序列化之后写入磁盘,等待被shuffer到一个可能完全不同的节点上。形式上认为这个过程采用的是值传递。发送的是键/值对副本。在目前的情况下,我们可以将一个Mapper与另一个相链接,在相同的JVM线程中一起执行。因此,键/值对的发送有可能采用引用传递,初始Mapper的输出放在内存中,后续的Mapper直接引用相同的内存位置。当Map1调用context.write(K k, V v)时,对象k和v直接传递给Map2的map方法。mapper之间可能有大量的数据需要传递,避免复制这些数据可以让性能得以提高。但是这么做违背了Hadoop中MapReduce Model的一个微妙约定:对Context.write(K k, V v)的调用一定不会改变k和v的内容。
如果我们可以确信Map1的map()方法在调用Context.write(k, v)之后不会再使用k和v的内容,或者Map2并不改变k和v在其上的输入值,可以通过设定byValue为false来获得一定的性能提升。如果我们对Mapper的内部代码不太了解,最好依旧采用值传递,确保Mapper会按预期的方式工作。