hadoop之二次排序

mr自帶的例子中的源碼SecondarySort,我重新寫了一下,基本沒變。

這個例子中定義的map和reduce如下,關鍵是它對輸入輸出類型的定義:(java泛型編程)

public static class Map extends Mapper<LongWritable, Text, IntPair, IntWritable>
public static class Reduce extends Reducer<IntPair, NullWritable, IntWritable, IntWritable>

1 首先說一下工作原理:

在map階段,使用job.setInputFormatClass定義的InputFormat將輸入的數據集分割成小數據塊splites,同時InputFormat提供一個RecordReder的實現。本例子中使用的是TextInputFormat,他提供的RecordReder會將文本的一行的行號作爲key,這一行的文本作爲value。這就是自定義Map的輸入是<LongWritable, Text>的原因。然後調用自定義Map的map方法,將一個個<LongWritable, Text>對輸入給Map的map方法。注意輸出應該符合自定義Map中定義的輸出<IntPair, IntWritable>。最終是生成一個List<IntPair, IntWritable>。在map階段的最後,會先調用job.setPartitionerClass對這個List進行分區,每個分區映射到一個reducer。每個分區內又調用job.setSortComparatorClass設置的key比較函數類排序。可以看到,這本身就是一個二次排序。如果沒有通過job.setSortComparatorClass設置key比較函數類,則使用key的實現的compareTo方法。在第一個例子中,使用了IntPair實現的compareTo方法,而在下一個例子中,專門定義了key比較函數類。
在reduce階段,reducer接收到所有映射到這個reducer的map輸出後,也是會調用job.setSortComparatorClass設置的key比較函數類對所有數據對排序。然後開始構造一個key對應的value迭代器。這時就要用到分組,使用jobjob.setGroupingComparatorClass設置的分組函數類。只要這個比較器比較的兩個key相同,他們就屬於同一個組,它們的value放在一個value迭代器,而這個迭代器的key使用屬於同一個組的所有key的第一個key。最後就是進入Reducer的reduce方法,reduce方法的輸入是所有的(key和它的value迭代器)。同樣注意輸入與輸出的類型必須與自定義的Reducer中聲明的一致。

2  二次排序就是首先按照第一字段排序,然後再對第一字段相同的行按照第二字段排序,注意不能破壞第一次排序結果 。例如

輸入文件
20 21
50 51
50 52
50 53
50 54
60 51
60 53
60 52
60 56
60 57
70 58
60 61
70 54
70 55
70 56
70 57
70 58
1 2
3 4
5 6
7 82
203 21
50 512
50 522
50 53
530 54
40 511
20 53
20 522
60 56
60 57
740 58
63 61
730 54
71 55
71 56
73 57
74 58
12 211
31 42
50 62
7 8
輸出:(注意需要分割線)
------------------------------------------------
1       2
------------------------------------------------
3       4
------------------------------------------------
5       6
------------------------------------------------
7       8
7       82
------------------------------------------------
12      211
------------------------------------------------
20      21
20      53
20      522
------------------------------------------------
31      42
------------------------------------------------
40      511
------------------------------------------------
50      51
50      52
50      53
50      53
50      54
50      62
50      512
50      522
------------------------------------------------
60      51
60      52
60      53
60      56
60      56
60      57
60      57
60      61
------------------------------------------------
63      61
------------------------------------------------
70      54
70      55
70      56
70      57
70      58
70      58
------------------------------------------------
71      55
71      56
------------------------------------------------
73      57
------------------------------------------------
74      58
------------------------------------------------
203     21
------------------------------------------------
530     54
------------------------------------------------
730     54
------------------------------------------------
740     58

3  具體步驟:
(1)自定義key

在mr中,所有的key是需要被比較和排序的,並且是二次,先根據partitione,再根據大小。而本例中也是要比較兩次。先按照第一字段排序,然後再對第一字段相同的按照第二字段排序。根據這一點,我們可以構造一個複合類IntPair,他有兩個字段,先利用分區對第一字段排序,再利用分區內的比較對第二字段排序。
所有自定義的key應該實現接口WritableComparable,因爲是可序列的並且可比較的。並重載方法:

  1. //反序列化,從流中的二進制轉換成IntPair 
  2. public void readFields(DataInput in) throws IOException         
  3. //序列化,將IntPair轉化成使用流傳送的二進制 
  4. public void write(DataOutput out) 
  5. //key的比較 
  6. public int compareTo(IntPair o)         
  7. //另外新定義的類應該重寫的兩個方法 
  8. //The hashCode() method is used by the HashPartitioner (the default partitioner in MapReduce) 
  9. public int hashCode()  
  10. public boolean equals(Object right) 
//反序列化,從流中的二進制轉換成IntPair
public void readFields(DataInput in) throws IOException        
//序列化,將IntPair轉化成使用流傳送的二進制
public void write(DataOutput out)
//key的比較
public int compareTo(IntPair o)        
//另外新定義的類應該重寫的兩個方法
//The hashCode() method is used by the HashPartitioner (the default partitioner in MapReduce)
public int hashCode() 
public boolean equals(Object right)

(2)由於key是自定義的,所以還需要自定義一下類:
(2.1)分區函數類。這是key的第一次比較。

  1. public static class FirstPartitioner extends Partitioner<IntPair,IntWritable> 
 public static class FirstPartitioner extends Partitioner<IntPair,IntWritable>

在job中使用setPartitionerClasss設置Partitioner。
(2.2)key比較函數類。這是key的第二次比較。這是一個比較器,需要繼承WritableComparator。

  1. public static class KeyComparator extends WritableComparator 
public static class KeyComparator extends WritableComparator

必須有一個構造函數,並且重載 public int compare(WritableComparable w1, WritableComparable w2)

另一種方法是 實現接口RawComparator。
在job中使用setSortComparatorClass設置key比較函數類。
(2.3)分組函數類。在reduce階段,構造一個key對應的value迭代器的時候,只要first相同就屬於同一個組,放在一個value迭代器。這是一個比較器,需要繼承WritableComparator。

  1. public static class GroupingComparator extends WritableComparator 
public static class GroupingComparator extends WritableComparator

分組函數類也必須有一個構造函數,並且重載 public int compare(WritableComparable w1, WritableComparable w2)
分組函數類的另一種方法是實現接口RawComparator。
在job中使用setGroupingComparatorClass設置分組函數類。

另外注意的是,如果reduce的輸入與輸出不是同一種類型,則不要定義Combiner也使用reduce,因爲Combiner的輸出是reduce的輸入。除非重新定義一個Combiner。

3 代碼。

這個例子中沒有使用key比較函數類,而是使用key的實現的compareTo方法。

  1. package secondarySort; 
  2. import java.io.DataInput; 
  3. import java.io.DataOutput; 
  4. import java.io.IOException; 
  5. import java.util.StringTokenizer; 
  6. import org.apache.hadoop.conf.Configuration; 
  7. import org.apache.hadoop.fs.Path; 
  8. import org.apache.hadoop.io.IntWritable; 
  9. import org.apache.hadoop.io.LongWritable; 
  10. import org.apache.hadoop.io.Text; 
  11. import org.apache.hadoop.io.WritableComparable; 
  12. import org.apache.hadoop.io.WritableComparator; 
  13. import org.apache.hadoop.mapreduce.Job; 
  14. import org.apache.hadoop.mapreduce.Mapper; 
  15. import org.apache.hadoop.mapreduce.Partitioner; 
  16. import org.apache.hadoop.mapreduce.Reducer; 
  17. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 
  18. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; 
  19. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 
  20. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; 
  21.  
  22. public class SecondarySort { 
  23.     //自己定義的key類應該實現WritableComparable接口 
  24.     public static class IntPair implements WritableComparable<IntPair> { 
  25.         int first; 
  26.         int second; 
  27.         /**
  28.          * Set the left and right values.
  29.          */ 
  30.         public void set(int left, int right) { 
  31.             first = left; 
  32.             second = right; 
  33.         } 
  34.         public int getFirst() { 
  35.             return first; 
  36.         } 
  37.         public int getSecond() { 
  38.             return second; 
  39.         } 
  40.         @Override 
  41.         //反序列化,從流中的二進制轉換成IntPair 
  42.         public void readFields(DataInput in) throws IOException { 
  43.             // TODO Auto-generated method stub 
  44.             first = in.readInt(); 
  45.             second = in.readInt(); 
  46.         } 
  47.         @Override 
  48.         //序列化,將IntPair轉化成使用流傳送的二進制 
  49.         public void write(DataOutput out) throws IOException { 
  50.             // TODO Auto-generated method stub 
  51.             out.writeInt(first); 
  52.             out.writeInt(second); 
  53.         } 
  54.         @Override 
  55.         //key的比較 
  56.         public int compareTo(IntPair o) { 
  57.             // TODO Auto-generated method stub 
  58.             if (first != o.first) { 
  59.                 return first < o.first ? -1 : 1
  60.             } else if (second != o.second) { 
  61.                 return second < o.second ? -1 : 1
  62.             } else
  63.                 return 0
  64.             } 
  65.         } 
  66.          
  67.         //新定義類應該重寫的兩個方法 
  68.         @Override 
  69.         //The hashCode() method is used by the HashPartitioner (the default partitioner in MapReduce) 
  70.         public int hashCode() { 
  71.             return first * 157 + second; 
  72.         } 
  73.         @Override 
  74.         public boolean equals(Object right) { 
  75.             if (right == null
  76.                 return false
  77.             if (this == right) 
  78.                 return true
  79.             if (right instanceof IntPair) { 
  80.                 IntPair r = (IntPair) right; 
  81.                 return r.first == first && r.second == second; 
  82.             } else
  83.                 return false
  84.             } 
  85.         } 
  86.     } 
  87.      /**
  88.        * 分區函數類。根據first確定Partition。
  89.        */ 
  90.       public static class FirstPartitioner extends Partitioner<IntPair,IntWritable>{ 
  91.         @Override 
  92.         public int getPartition(IntPair key, IntWritable value,  
  93.                                 int numPartitions) { 
  94.           return Math.abs(key.getFirst() * 127) % numPartitions; 
  95.         } 
  96.       } 
  97.        
  98.       /**
  99.        * 分組函數類。只要first相同就屬於同一個組。
  100.        */ 
  101.     /*//第一種方法,實現接口RawComparator
  102.     public static class GroupingComparator implements RawComparator<IntPair> {
  103.         @Override
  104.         public int compare(IntPair o1, IntPair o2) {
  105.             int l = o1.getFirst();
  106.             int r = o2.getFirst();
  107.             return l == r ? 0 : (l < r ? -1 : 1);
  108.         }
  109.         @Override
  110.         //一個字節一個字節的比,直到找到一個不相同的字節,然後比這個字節的大小作爲兩個字節流的大小比較結果。
  111.         public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2){
  112.             // TODO Auto-generated method stub
  113.              return WritableComparator.compareBytes(b1, s1, Integer.SIZE/8,
  114.                      b2, s2, Integer.SIZE/8);
  115.         }
  116.     }*/ 
  117.     //第二種方法,繼承WritableComparator 
  118.     public static class GroupingComparator extends WritableComparator { 
  119.           protected GroupingComparator() { 
  120.             super(IntPair.class, true); 
  121.           } 
  122.           @Override 
  123.           //Compare two WritableComparables. 
  124.           public int compare(WritableComparable w1, WritableComparable w2) { 
  125.             IntPair ip1 = (IntPair) w1; 
  126.             IntPair ip2 = (IntPair) w2; 
  127.             int l = ip1.getFirst(); 
  128.             int r = ip2.getFirst(); 
  129.             return l == r ? 0 : (l < r ? -1 : 1); 
  130.           } 
  131.         } 
  132.      
  133.          
  134.     // 自定義map 
  135.     public static class Map extends 
  136.             Mapper<LongWritable, Text, IntPair, IntWritable> { 
  137.         private final IntPair intkey = new IntPair(); 
  138.         private final IntWritable intvalue = new IntWritable(); 
  139.         public void map(LongWritable key, Text value, Context context) 
  140.                 throws IOException, InterruptedException { 
  141.             String line = value.toString(); 
  142.             StringTokenizer tokenizer = new StringTokenizer(line); 
  143.             int left = 0
  144.             int right = 0
  145.             if (tokenizer.hasMoreTokens()) { 
  146.                 left = Integer.parseInt(tokenizer.nextToken()); 
  147.                 if (tokenizer.hasMoreTokens()) 
  148.                     right = Integer.parseInt(tokenizer.nextToken()); 
  149.                 intkey.set(left, right); 
  150.                 intvalue.set(right); 
  151.                 context.write(intkey, intvalue); 
  152.             } 
  153.         } 
  154.     } 
  155.     // 自定義reduce 
  156.     // 
  157.     public static class Reduce extends 
  158.             Reducer<IntPair, IntWritable, Text, IntWritable> { 
  159.         private final Text left = new Text(); 
  160.         private static final Text SEPARATOR =  
  161.               new Text("------------------------------------------------"); 
  162.         public void reduce(IntPair key, Iterable<IntWritable> values, 
  163.                 Context context) throws IOException, InterruptedException { 
  164.             context.write(SEPARATOR, null); 
  165.             left.set(Integer.toString(key.getFirst())); 
  166.             for (IntWritable val : values) { 
  167.                 context.write(left, val); 
  168.             } 
  169.         } 
  170.     } 
  171.     /**
  172.      * @param args
  173.      */ 
  174.     public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException { 
  175.         // TODO Auto-generated method stub 
  176.         // 讀取hadoop配置 
  177.         Configuration conf = new Configuration(); 
  178.         // 實例化一道作業 
  179.         Job job = new Job(conf, "secondarysort"); 
  180.         job.setJarByClass(SecondarySort.class); 
  181.         // Mapper類型 
  182.         job.setMapperClass(Map.class); 
  183.         // 不再需要Combiner類型,因爲Combiner的輸出類型<Text, IntWritable>對Reduce的輸入類型<IntPair, IntWritable>不適用 
  184.         //job.setCombinerClass(Reduce.class); 
  185.         // Reducer類型 
  186.         job.setReducerClass(Reduce.class); 
  187.         // 分區函數 
  188.         job.setPartitionerClass(FirstPartitioner.class); 
  189.         // 分組函數 
  190.         job.setGroupingComparatorClass(GroupingComparator.class); 
  191.          
  192.         // map 輸出Key的類型 
  193.         job.setMapOutputKeyClass(IntPair.class); 
  194.         // map輸出Value的類型 
  195.         job.setMapOutputValueClass(IntWritable.class); 
  196.         // rduce輸出Key的類型,是Text,因爲使用的OutputFormatClass是TextOutputFormat 
  197.         job.setOutputKeyClass(Text.class); 
  198.         // rduce輸出Value的類型 
  199.         job.setOutputValueClass(IntWritable.class); 
  200.          
  201.         // 將輸入的數據集分割成小數據塊splites,同時提供一個RecordReder的實現。 
  202.         job.setInputFormatClass(TextInputFormat.class); 
  203.         // 提供一個RecordWriter的實現,負責數據輸出。 
  204.         job.setOutputFormatClass(TextOutputFormat.class); 
  205.          
  206.         // 輸入hdfs路徑 
  207.         FileInputFormat.setInputPaths(job, new Path(args[0])); 
  208.         // 輸出hdfs路徑 
  209.         FileOutputFormat.setOutputPath(job, new Path(args[1])); 
  210.         // 提交job 
  211.         System.exit(job.waitForCompletion(true) ? 0 : 1); 
  212.     } 

發佈了26 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章