Combiner編程(1.5可選步驟,視情況而定!)
- 每一個map可能會產生大量的輸出,combiner的作用就是在map端對輸出先做一次合並,以減少傳輸到reducer的數據量。
- combiner最基本是實現本地key的歸並,combiner具有類似本地的reduce功能。 如果不用combiner,那么,所有的結果
都是reduce完成,效率會相對低下。使用combiner,先完成的map會在本地聚合,提升速度。
- 注意:Combiner的輸出是Reducer的輸入,Combiner絕不能改變最終的計算結果。所以從我的想法來看,Combiner只應該
用於那種Reduce的輸入key/value與輸出key/value類型完全一致,且不影響最終結果的場景。比如累加,最大值等。
1 package combine; 2 3 import java.net.URI; 4 5 import org.apache.hadoop.conf.Configuration; 6 import org.apache.hadoop.fs.FileSystem; 7 import org.apache.hadoop.fs.Path; 8 import org.apache.hadoop.io.LongWritable; 9 import org.apache.hadoop.io.Text; 10 import org.apache.hadoop.mapreduce.Job; 11 import org.apache.hadoop.mapreduce.Mapper; 12 import org.apache.hadoop.mapreduce.Reducer; 13 import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 14 import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 15 16 /** 17 * 問:為什么使用Combiner? 18 * 答:Combiner發生在Map端,對數據進行規約處理,數據量變小了,傳送到reduce端的數據量變小了,傳輸時間變短,作業的整體時間變短。 19 * 20 * 問:為什么Combiner不作為MR運行的標配,而是可選步驟呢? 21 * 答:因為不是所有的算法都適合使用Combiner處理,例如求平均數。 22 * 23 * 問:Combiner本身已經執行了reduce操作,為什么在Reducer階段還要執行reduce操作呢? 24 * 答:combiner操作發生在map端的,處理一個任務所接收的文件中的數據,不能跨map任務執行;只有reduce可以接收多個map任務處理的數據。 25 * 26 */ 27 public class WordCountApp { 28 static final String INPUT_PATH = "hdfs://chaoren:9000/hello"; 29 static final String OUT_PATH = "hdfs://chaoren:9000/out"; 30 31 public static void main(String[] args) throws Exception { 32 Configuration conf = new Configuration(); 33 final FileSystem fileSystem = FileSystem.get(new URI(INPUT_PATH), conf); 34 final Path outPath = new Path(OUT_PATH); 35 if(fileSystem.exists(outPath)){ 36 fileSystem.delete(outPath, true); 37 } 38 39 final Job job = new Job(conf , WordCountApp.class.getSimpleName()); 40 //1.1指定讀取的文件位於哪里 41 FileInputFormat.setInputPaths(job, INPUT_PATH); 42 //指定如何對輸入文件進行格式化,把輸入文件每一行解析成鍵值對 43 //job.setInputFormatClass(TextInputFormat.class); 44 45 //1.2 指定自定義的map類 46 job.setMapperClass(MyMapper.class); 47 //map輸出的<k,v>類型。如果<k3,v3>的類型與<k2,v2>類型一致,則可以省略 48 //job.setMapOutputKeyClass(Text.class); 49 //job.setMapOutputValueClass(LongWritable.class); 50 51 //1.3 分區 52 //job.setPartitionerClass(HashPartitioner.class); 53 //有一個reduce任務運行 54 //job.setNumReduceTasks(1); 55 56 //1.4 TODO 排序、分組 57 58 //1.5 規約 59 job.setCombinerClass(MyCombiner.class); 60 61 //2.2 指定自定義reduce類 62 job.setReducerClass(MyReducer.class); 63 //指定reduce的輸出類型 64 job.setOutputKeyClass(Text.class); 65 job.setOutputValueClass(LongWritable.class); 66 67 //2.3 指定寫出到哪里 68 FileOutputFormat.setOutputPath(job, outPath); 69 //指定輸出文件的格式化類 70 //job.setOutputFormatClass(TextOutputFormat.class); 71 72 //把job提交給JobTracker運行 73 job.waitForCompletion(true); 74 } 75 76 /** 77 * KEYIN 即k1 表示行的偏移量 78 * VALUEIN 即v1 表示行文本內容 79 * KEYOUT 即k2 表示行中出現的單詞 80 * VALUEOUT 即v2 表示行中出現的單詞的次數,固定值1 81 */ 82 static class MyMapper extends Mapper<LongWritable, Text, Text, LongWritable>{ 83 protected void map(LongWritable k1, Text v1, Context context) throws java.io.IOException ,InterruptedException { 84 final String[] splited = v1.toString().split("\t"); 85 for (String word : splited) { 86 context.write(new Text(word), new LongWritable(1)); 87 System.out.println("Mapper輸出<"+word+","+1+">"); 88 } 89 }; 90 } 91 92 /** 93 * KEYIN 即k2 表示行中出現的單詞 94 * VALUEIN 即v2 表示行中出現的單詞的次數 95 * KEYOUT 即k3 表示文本中出現的不同單詞 96 * VALUEOUT 即v3 表示文本中出現的不同單詞的總次數 97 * 98 */ 99 static class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable>{ 100 protected void reduce(Text k2, java.lang.Iterable<LongWritable> v2s, Context ctx) throws java.io.IOException ,InterruptedException { 101 //顯示次數表示redcue函數被調用了多少次,表示k2有多少個分組 102 System.out.println("MyReducer輸入分組<"+k2.toString()+",...>"); 103 long times = 0L; 104 for (LongWritable count : v2s) { 105 times += count.get(); 106 //顯示次數表示輸入的k2,v2的鍵值對數量 107 System.out.println("MyReducer輸入鍵值對<"+k2.toString()+","+count.get()+">"); 108 } 109 ctx.write(k2, new LongWritable(times)); 110 }; 111 } 112 113 114 static class MyCombiner extends Reducer<Text, LongWritable, Text, LongWritable>{ 115 protected void reduce(Text k2, java.lang.Iterable<LongWritable> v2s, Context ctx) throws java.io.IOException ,InterruptedException { 116 //顯示次數表示redcue函數被調用了多少次,表示k2有多少個分組 117 System.out.println("Combiner輸入分組<"+k2.toString()+",...>"); 118 long times = 0L; 119 for (LongWritable count : v2s) { 120 times += count.get(); 121 //顯示次數表示輸入的k2,v2的鍵值對數量 122 System.out.println("Combiner輸入鍵值對<"+k2.toString()+","+count.get()+">"); 123 } 124 125 ctx.write(k2, new LongWritable(times)); 126 //顯示次數表示輸出的k2,v2的鍵值對數量 127 System.out.println("Combiner輸出鍵值對<"+k2.toString()+","+times+">"); 128 }; 129 } 130 }