4.2.2 總排序(Total order sorting)
有的時候需要將作業的的所有輸出進行總排序,使各個輸出之間的結果是有序的。有以下實例:
- 如果要得到某個網站中最受歡迎的網址(URL),就需要根據某種受歡迎的指標來對網址進行排序。
- 如果要讓最活躍的用戶能夠看到某張表,就需要根據某種標准(發表文章數)對用戶進行排序。
技術22 在多個reduce間對鍵進行排序
在MapReduce框架中,map的輸出會被排序,然后被發送給reduce。不過,相同reduce的輸入數據是有序的,不同reduce的輸入數據就沒有順序關系了。如果要讓不同的reduce的數據也存在順序關系,就需要使用分區器(partitioner)。MapReduce的默認分區器是HashPartitioner。它使用map的輸出鍵的哈希值進行分區。這保證了相同的map輸出鍵的所有記錄會到達同一個reduce。不過HashPartitioner並不會對所有map的全部輸出鍵進行總排序。接下來說明如何在MapReduce中對所有map的全部輸出鍵進行排序:
問題
需要對作業輸出的所有鍵進行總排序,但是不能增加任何一個reduce的負擔。
方案
這里要用到TotalOrderPartitioner類來保證所有reduce的全部輸出是有序的。這個類由Hadoop自帶。這個分類器保證了所有map的全部輸出是完全有序的。那么只要reduce的輸出鍵和輸入鍵是一樣的,作業的最終輸出就是有序的。
討論
TotalOrderPartitioner是Hadoop的內置分區器。它根據分區文件進行分區。分區文件是一個包括N-1個鍵的預先計算好的序列文件。(N是指reduce的個數。)分區文件中的鍵的順序是由map輸出鍵比較器決定的。每一個鍵對應着一個邏輯區間。TotalOrderPartitioner檢查每一個輸出鍵,確定它在那個區間,然后將這個鍵發送給相對應的reduce。
圖4.15中說明了這個技術的兩個部分。第一部分,創建分區文件。第二部分,將TotalOrderPartitioner加入MapReduce作業。
先用InputSampler從輸入文件中抽樣,以生成分區文件。抽樣器可以選用RandomSampler類進行隨機抽樣,也可以選用IntervalSampler類進行間距為R的等距抽樣。生成的分區文件中將包含有序的N-1個鍵。N是reduce的個數。InputSampler不是MapReduce作業。它從InputFormat讀取數據。它在被調用的過程中生成分區。
下列代碼說明了在調用InputSampler函數之前需要完成的步驟:
1 int numReducers = 2; 2 3 Path input = new Path(args[0]); 4 5 Path partitionFile = new Path(args[1]); 6 7 InputSampler.Sampler<Text, Text> sampler = new InputSampler.RandomSampler<Text,Text>(0.1, 10000, 10); 8 9 JobConf job = new JobConf(); 10 11 job.setNumReduceTasks(numReducers); 12 13 job.setInputFormat(KeyValueTextInputFormat.class); 14 15 job.setMapOutputKeyClass(Text.class); 16 17 job.setMapOutputValueClass(Text.class); 18 19 TotalOrderPartitioner.setPartitionFile(job, partitionFile); 20 21 FileInputFormat.setInputPaths(job, input); 22 23 InputSampler.writePartitionFile(job, sampler);
下一步在作業中指定TotalOrderPartitioner為分區器:
1 job.setPartitionerClass(TotalOrderPartitioner.class);
這個技術並不需要修改MapReduce作業本身,也就是說,不需要修改map或reduce過程。現在就可以開始運行代碼了:
$ hadoop fs -put test-data/names.txt names.txt $ bin/run.sh com.manning.hip.ch4.sort.total.TotalSortMapReduce \ names.txt \ large-names-sampled.txt \ output $ hadoop fs -ls output /user/aholmes/output/part-00000 /user/aholmes/output/part-00001 $ hadoop fs -cat output/part-00000 | head AABERG AABY AADLAND $ hadoop fs -cat output/part-00000 | tail LANCZ LAND LANDA $ hadoop fs -cat output/part-00001 | head LANDACRE LANDAKER LANDAN $ hadoop fs -cat output/part-00001 | tail ZYSK ZYSKOWSKI ZYWIEC
從MapReduce作業的結果中可以看到,在各個輸出文件之間,map的輸出鍵是有序的。
小結
這個技術中使用InputSampler來創建分區文件。TotalOrderPartitioner使用這個分區文件來分區map的輸出鍵。
MapReduce也可以生成分區文件,但效率不高。另一個有效的的方法就是用自定義的InputFormat類來執行抽樣,並將抽樣后的鍵發送給一個reduce,由其創建分區文件。這也就是這一章下一個部分講到的抽樣。