hadoop基礎部分的學習告一段落,休息了幾天,現在滿血復活了。。。哈哈,讓我們一起來學習學習hadoop的第一個鏈接MapReduce作業的案例吧。
在高階數據處理中,會經常發現無法將整個流程寫在單個MapReduce作業中,Hadoop支持將多個MapReduce程序鏈接成更大的作業。
1、順序鏈接MapReduce作業
雖然兩個作業可以手動的逐個執行,但更爲快捷的方式是生成一個自動化的執行序列,可以將MapReduce作業按照作業順序鏈接在一起,用一個MapReduce作業的輸出作爲下一個的輸入。MapReduce作業鏈接類似與Unix管道
mapreduce-1 | mapreduce-2 | mapreduce-3 | ...
每個作業的driver都必須創建一個新的job,並將當前輸入路徑設爲前一個的輸出。在最後階段刪除在鏈上每個階段生成的中間數據。
2、複雜依賴的MapReduce鏈接
在複雜數據處理任務中的子任務並不是按順序運行的,則它們的MapReduce作業不能按線性方式鏈接。
若mapreduce-1處理一個數據集,MapReduce-2處理另一個數據集,而MapReduce-3對前兩個做內部連結,則MapReduce-3依賴於其他兩個作業,僅當MapReduce-1和MapReduce-2都完成後纔可以執行,而MapReduce-1和MapReduce-2之間並無相互依賴。
hadoop的簡化機制,通過Job和JobControl類管理非線性作業間的依賴。Job對象是MapReduce作業的表現形式,Job對象的實例化可通過傳遞一個JobConf對象到作業的構造函數來實現,除了要保持作業的配置信息外,job還通過設定addDependingJob()方法維護作業的依賴關係。如x.addDependingJob(y)意味着x在y完成前不會啓動。3、預處理和後處理階段的鏈接
大量的數據處理任務涉及對記錄的預處理和後處理。可以爲預處理與後處理步驟各自編寫一個MapReduce作業,並把他們鏈接起來,由於過程中每一個步驟的中間結果都需要佔用I/O和存儲資源,這種做法是低效的,也可以自己寫mapper去預先調用所有的預處理步驟,在讓reducer調用所有的後處理步驟,這將強制你採用模塊化和可組合的方式來構建預處理和後處理。Hadoop在版本0.19.0中引入了ChainMapper和ChainReducer類來簡化預處理和後處理的構成。
可以通過僞正則表達式將MapReduce作業的鏈接符合化的表達爲:
[MAP | REDUCE] +
使用ChainMapper和ChainReduce所生成的作業表達式與此類似:
MAP+ | REDUCE | MAP+
作業按序執行多個mapper來預處理數據,並在運行reduce之後可選的按序執行多個mapper來做數據的後處理。
有這樣一個例子:有4個mappper(Map1,Map2,Map3,Map4)和一個reducer(Reduce),它們被鏈接爲單個MapReduce作業。
順序以下: Map1 | Map2 | Reduce | Map3 | Map4
在這個組合中,可以把Map2和Reduce視爲MapReduce作業的核心,在Mapper和Reducer之間使用標準的分區和洗牌。把Map1作爲前處理步驟,Map3, Map4作爲後處理步驟。
在driver中使用ChainMapper和ChainReducer類來設定這個mapper類和reducer類序列的構成。
ChainMapper使用模式:(預處理作業)
ChainReducer使用模式:(設置Reducer並添加後處理Mapper)
代碼清單:MapReduce作業鏈接(舊版本API)
package com.yc.demo;
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
import org.apache.hadoop.mapred.lib.ChainMapper;
import org.apache.hadoop.mapred.lib.ChainReducer;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class MyJObLink extends Configured implements Tool {
public static class Reduce extends MapReduceBase implements Reducer<LongWritable,Text,Text,Text>{
@Override
public void reduce(LongWritable key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter)
throws IOException {
output.collect(new Text("1"), new Text("1"));
}
}
public static class Map1 extends MapReduceBase implements Mapper<LongWritable,Text,Text,Text> {
@Override
public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter)
throws IOException {
output.collect(value, new Text(key.toString()));//V1(記錄)作鍵K2,K1(偏移量)作值V2
}
}
public static class Map2 extends MapReduceBase implements Mapper<Text,Text,LongWritable,Text>{
@Override
public void map(Text key, Text value, OutputCollector<LongWritable, Text> output, Reporter reporter)
throws IOException {
output.collect(new LongWritable(Long.valueOf(value.toString())),key);//輸入鍵值對,交換後作鍵值對輸出
}
}
public static class Map3 extends MapReduceBase implements Mapper<Text,Text,LongWritable,Text> {
@Override
public void map(Text key, Text value, OutputCollector<LongWritable, Text> output, Reporter reporter)
throws IOException {
output.collect(new LongWritable(Long.valueOf("1")),key);//輸入鍵值對後輸出鍵爲1,值爲輸入鍵
}
}
public static class Map4 extends MapReduceBase implements Mapper<LongWritable,Text,LongWritable,Text>{
@Override
public void map(LongWritable key, Text value, OutputCollector<LongWritable, Text> output, Reporter reporter)
throws IOException {
output.collect(new LongWritable(Long.valueOf("1")), new Text("1"));//輸入鍵值對後輸出鍵爲1,值爲1
}
}
@Override
public int run(String[] args) throws Exception {
//1、實例化作業對象
Configuration conf = getConf();
JobConf job = new JobConf(conf);
job.setJobName("xiaoxiaoChainJob");
//2、爲作業設置輸入文本格式化和輸出文本的格式化
job.setInputFormat(TextInputFormat.class);
job.setOutputFormat(TextOutputFormat.class);
//3、爲作業設置輸入文件和輸出文件的路徑
Path in = new Path(args[0]);
Path out = new Path(args[1]);
FileInputFormat.setInputPaths(job, in);
FileOutputFormat.setOutputPath(job, out);
/**
* 在作業添加Map1階段
* 使用ChainMapper.addMapper()添加Reduce之前的所有步驟
*
* ChainMapper.addMapper(JobConf job,Class<? extends Mapper<LongWritable,Text,Text,Text>> klass,
* Class<? extends LongWritable> inputKeyClass,Class<? extends Text> outputKeyClass,Class<? extends Text> outputValueClass,
* boolean byValue,JobConf mapperConf)
* 該方法有8個參數,第一個和最後一個分別爲全局和本地JobConf對象,第二個參數(klass)是Mapper類,負責數據處理
* 餘下4個參數inputKeyClass,inputValueClass,outputKeyClass,outputValueClass是這個Mapper類中輸入/輸出類的類型
* byValue這個參數,在標準的Mapper模型中,鍵/值對的輸出在序列化之後寫入磁盤(鍵和值實現爲Writable使得它們能夠被複制和序列化),
* 等待被洗牌到一個可能完全不同的節點上,形式上認爲這個過程採用的是值傳遞(passed by value),發送的是鍵值對的副本
* 在目前的情況下我們可以將一個Mapper與另一個相鏈接,在相同的JVM線程中一起執行,因此,鍵/值對的發送有可能採用引用傳遞(passed by reference)
* 初始Mapper的輸出放到內存中,後續的Mapper直接引用相同的內存位置
* 當Mapper 1調用OutputCollector.collect<K k,V v)時,對象k和v直接傳遞給Map2的map()方法,
* mapper之間可能有大量的數據需要傳遞,避免去複製這些數據可以讓性能得以提高.
* 但是,這樣會違背Hadoop中MapReduceApi的一個更爲微妙的"約定",即對OutputCollector.collect(K k,V v)
* 的調用一定不會改變k和v的內容.
* Map1調用OutputCollector.collect(K k,V v)之後,可以繼續使用對象k和v,並完全相信他們的值會保持不變.
* 但如果我們將這些對象通過引用傳遞給Map2,接下來Map2可能會改變他們,這就違反了API的"約定".
* 如果你確信Map1的map()方法在調用OutputCollector.collect(K k,V v)之後不再使用k和v的內容,
* 或者Map2並不改變k和v的在其上的輸入值,你可以通過設定byValue爲false來獲得一定的性能提升.
* 如果你對Mapper的內部代碼不太瞭解,安全起見最好設byValue爲true,依舊採用值傳遞模式,
* 確保mapper會按預期的方式工作.
*/
//4、爲作業設置Mapper和Reducer函數
//(1)在作業中添加Map1階段,使用ChainMapper.addMapper()添加位於Reduce之前的步驟
JobConf map1Conf = new JobConf(false);
ChainMapper.addMapper(job, Map1.class,LongWritable.class,Text.class,Text.class,Text.class, true,map1Conf);
/**
* 在作業中添加Map2階段
* 使用ChainMapper.addMapper()添加位於Reduce之前的所有步驟
*/
JobConf map2Conf=new JobConf(false);
ChainMapper.addMapper(job, Map2.class, Text.class, Text.class, LongWritable.class, Text.class, true, map2Conf);
/**
* 在作業中添加Reduce階段
* 使用靜態的ChainReducer.setReducer()方法設置reducer
*/
JobConf reducerConf = new JobConf(false);
ChainReducer.setReducer(job, Reduce.class, LongWritable.class, Text.class, Text.class, Text.class, true, reducerConf);
/**
* 在作業中添加Map3階段
* 使用ChainReducer.addMapper()添加reducer後續的步驟
*/
JobConf map3Conf=new JobConf(false);
ChainReducer.addMapper(job, Map3.class, Text.class, Text.class, LongWritable.class, Text.class, true, map3Conf);
/**
* 在作業中添加Map4階段
* 使用ChainReducer.addMapper()添加reducer後續的步驟
*/
JobConf map4Conf = new JobConf(false);
ChainReducer.addMapper(job, Map4.class,LongWritable.class,Text.class,LongWritable.class,Text.class,true,map4Conf);
//啓動作業
JobClient.runJob(job);
return 0;
}
public static void main(String [] args) throws Exception {
/*final String inputPath = "/home/dev/hadooptest/mapin/cite";
final String outputPath="/home/dev/hadooptest/mapin/cite/out";
String [] paths = {inputPath,outputPath};*/
/**
* Driver中的main函數->ToolRunner中的run函數->Too接口中的run函數->
* Driver中覆蓋函數處理參數->Driver中核心函數啓動job(合併爲一個方法,重寫了接口Tool的run方法)
* args(運行時動態給定的)、paths(代碼中定死的)爲輸入文本和輸出文本的路徑,
*/
int res=ToolRunner.run(new Configuration(), new MyJObLink(),args);
/*int res=ToolRunner.run(new Configuration(), new MyJObLink(),paths);*/
System.exit(res);
}
}
MapReduce作業鏈接(新版本API)
package com.yc.link;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.chain.ChainMapper;
import org.apache.hadoop.mapreduce.lib.chain.ChainReducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class MyJobLink extends Configured implements Tool{
/**
* Reducer任務 Reduce
* @author 王雲飛
*
*/
public static class Reduce extends Reducer<LongWritable,Text,Text,Text>{
@Override
public void reduce(LongWritable key, Iterable<Text> values,Context context) throws IOException, InterruptedException {
context.write(new Text("1"), new Text("1"));
}
}
/**
* Mapper任務 Map1
* @author 王雲飛
*
*/
public static class Map1 extends Mapper<LongWritable,Text,Text,Text>{
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
context.write(value, new Text(key.toString()));// V1(記錄)作鍵K2,K1(偏移量)作值V2
}
}
/**
* Mapper任務 Map2
* @author hadoop
*
*/
public static class Map2 extends Mapper<Text,Text,LongWritable,Text>{
@Override
public void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
context.write(new LongWritable(Long.valueOf(value.toString())), key);// 輸入鍵值對交換後作鍵值對輸出
}
}
/**
* Mapper任務 Map3
* @author 王雲飛
*
*/
public static class Map3 extends Mapper<Text,Text,LongWritable,Text>{
@Override
public void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
context.write(new LongWritable(Long.valueOf("1")), key);// 輸入鍵值對後輸出鍵爲1,值爲輸入鍵
}
}
/**
* Mapper任務 Map4
* @author 王雲飛
*
*/
public static class Map4 extends Mapper<LongWritable,Text,LongWritable,Text>{
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
context.write(new LongWritable(Long.valueOf("1")), new Text("1"));// 輸入鍵值對後輸出鍵爲1,值爲1
}
}
/**
* driver類
*/
@Override
public int run(String[] args) throws Exception {
// 1.實例化作業對象
Configuration conf=this.getConf();
Job job=new Job(conf,"飛哥ChainJob");
job.setJarByClass(MyJobLink.class);
// 2.爲作業設置輸入文件和輸出文件的路徑
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//3.爲作業設置輸入文本的格式化和輸出文本的格式化
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
// 4.爲作業設置Mapper 和Reducer函數
//(1)在作業中添加Map1階段, 使用ChainMapper.addMapper()添加位於Reduce之前的步驟
//(job, klass, inputKeyClass, inputValueClass, outputKeyClass, outputValueClass, mapperConf
Configuration map1Conf=new Configuration(false);
ChainMapper.addMapper( job,
Map1.class,
LongWritable.class,
Text.class,
Text.class,
Text.class,
map1Conf);
// (2)在作業中添加Map2階段, 使用ChainMapper.addMapper()添加位於Reduce之前的步驟
Configuration map2Conf=new Configuration(false);
ChainMapper.addMapper( job,
Map2.class,
Text.class,
Text.class,
LongWritable.class,
Text.class,
map2Conf);
//(3)在作業中添加Reduce階段,使用ChainReducer.setReducer()方法設置Reducer
Configuration reduceConf=new Configuration(false);
//job, klass, inputKeyClass, inputValueClass, outputKeyClass, outputValueClass, reducerConf
ChainReducer.setReducer(job,
Reduce.class,
LongWritable.class,
Text.class,
Text.class,
Text.class,
reduceConf);
//(4)在作業中添加Map3階段,使用ChainReducer.addMapper()添加reducer後續的步驟
Configuration map3Conf=new Configuration(false);
ChainReducer.addMapper( job,
Map3.class,
Text.class,
Text.class,
LongWritable.class,
Text.class,
map3Conf);
// (5)在作業中添加Map4階段,使用ChainReducer.addMapper()添加reducer後續的步驟
Configuration map4Conf=new Configuration(false);
ChainReducer.addMapper( job,
Map4.class,
LongWritable.class,
Text.class,
LongWritable.class,
Text.class,
map4Conf);
// 5.啓動作業
return (job.waitForCompletion(true)?0:1);
}
/**
* 主函數
* @param args
* @throws Exception
*/
public static void main(String [] args) throws Exception{
int res=ToolRunner.run(new Configuration(), new MyJobLink(), args);
System.exit(res);
}
}
和小夥伴一人寫了一個版本的,但輸出結果是一樣的,輸出結果如下: