1.Hadoop集羣搭建
安裝
安裝Hadoop集羣通常要將安裝軟件解壓到集羣內的所有機器上。
通常,集羣裏的一臺機器被指定爲 NameNode,另一臺不同的機器被指定爲JobTracker。這些機器是masters。餘下的機器既作爲DataNode也作爲TaskTracker。這些機器是slaves。
我們用HADOOP_HOME指代安裝的根路徑。通常,集羣裏的所有機器的HADOOP_HOME路徑相同。
配置
配置文件
對Hadoop的配置通過conf/目錄下的兩個重要配置文件完成:
1.hadoop-default.xml - 只讀的默認配置。
2.hadoop-site.xml - 集羣特有的配置。
此外,通過設置conf/hadoop-env.sh中的變量爲集羣特有的值,你可以對bin/目錄下的Hadoop腳本進行控制。
集羣配置
要配置Hadoop集羣,你需要設置Hadoop守護進程的運行環境和Hadoop守護進程的運行參數。
Hadoop守護進程指NameNode/DataNode 和JobTracker/TaskTracker。
Slaves:
在conf/slaves文件中列出所有slave的主機名或者IP地址,一行一個。
2.Hadoop分佈式文件系統:架構和設計
Hadoop分佈式文件系統(HDFS)被設計成適合運行在通用硬件(commodity hardware)上的分佈式文件系統。HDFS是一個高度容錯性的系統,適合部署在廉價的機器上。HDFS能提供高吞吐量的數據訪問,非常適合大規模數據集上的應用。HDFS放寬了一部分POSIX約束,來實現流式讀取文件系統數據的目的。
前提和設計目標
硬件錯誤
硬件錯誤是常態而不是異常。我們面對的現實是構成系統的組件數目是巨大的,而且任一組件都有可能失效,這意味着總是有一部分HDFS的組件是不工作的。因此錯誤檢測和快速、自動的恢復是HDFS最核心的架構目標。
流式數據訪問
運行在HDFS上的應用和普通的應用不同,需要流式訪問它們的數據集。HDFS的設計中更多的考慮到了數據批處理,而不是用戶交互處理。
比之數據訪問的低延遲問題,更關鍵的在於數據訪問的高吞吐量。
大規模數據集
運行在HDFS上的應用具有很大的數據集。HDFS上的一個典型文件大小一般都在G字節至T字節。因此,HDFS被調節以支持大文件存儲。它應該能提供整體上高的數據傳輸帶寬,能在一個集羣裏擴展到數百個節點。一個單一的HDFS實例應該能支撐數以千萬計的文件。
簡單的一致性模型
HDFS應用需要一個“一次寫入多次讀取”的文件訪問模型。一個文件經過創建、寫入和關閉之後就不需要改變。這一假設簡化了數據一致性問題,並且使高吞吐量的數據訪問成爲可能。Map/Reduce應用或者網絡爬蟲應用都非常適合這個模型。
“移動計算比移動數據更划算”
一個應用請求的計算,離它操作的數據越近就越高效,在數據達到海量級別的時候更是如此。因爲這樣就能降低網絡阻塞的影響,提高系統數據的吞吐量。將計算移動到數據附近,比之將數據移動到應用所在顯然更好。
Namenode 和 Datanode
架構圖:
數據複製
HDFS被設計成能夠在一個大集羣中跨機器可靠地存儲超大文件。它將每個文件存儲成一系列的數據塊,除了最後一個,所有的數據塊都是同樣大小的。同時,爲了容錯,文件的所有數據塊都有副本。
Namenode全權管理數據塊的複製,它週期性地從集羣中的每個Datanode接收心跳信號和塊狀態報告(Blockreport)。接收到心跳信號意味着該Datanode節點工作正常。塊狀態報告包含了一個該Datanode上所有數據塊的列表。
副本存放: 最最開始的一步
副本的存放是HDFS可靠性和性能的關鍵。
下面是默認的副本策略:
在大多數情況下,副本系數是3,HDFS的存放策略是將一個副本存放在本地機架的節點上,一個副本放在同一機架的另一個節點上,最後一個副本放在不同機架的節點上。這種策略減少了機架間的數據傳輸,這就提高了寫操作的效率。機架的錯誤遠遠比節點的錯誤少,所以這個策略不會影響到數據的可靠性和可用性。於此同時,因爲數據塊只放在兩個(不是三個)不同的機架上,所以此策略減少了讀取數據時需要的網絡傳輸總帶寬。在這種策略下,副本並不是均勻分佈在不同的機架上。三分之一的副本在一個節點上,三分之二的副本在一個機架上,其他副本均勻分佈在剩下的機架中,這一策略在不損害數據可靠性和讀取性能的情況下改進了寫的性能。
副本選擇
爲了降低整體的帶寬消耗和讀取延時,HDFS會盡量讓讀取程序讀取離它最近的副本。如果在讀取程序的同一個機架上有一個副本,那麼就讀取該副本。如果一個HDFS集羣跨越多個數據中心,那麼客戶端也將首先讀本地數據中心的副本。
健壯性
集羣均衡
DFS的架構支持數據均衡策略。如果某個Datanode節點上的空閒空間低於特定的臨界點,按照均衡策略系統就會自動地將數據從這個Datanode移動到其他空閒的Datanode。當對某個文件的請求突然增加,那麼也可能啓動一個計劃創建該文件新的副本,並且同時重新平衡集羣中的其他數據。
數據完整性
從某個Datanode獲取的數據塊有可能是損壞的,損壞可能是由Datanode的存儲設備錯誤、網絡錯誤或者軟件bug造成的。HDFS客戶端軟件實現了對HDFS文件內容的校驗和(checksum)檢查。當客戶端創建一個新的HDFS文件,會計算這個文件每個數據塊的校驗和,並將校驗和作爲一個單獨的隱藏文件保存在同一個HDFS名字空間下。當客戶端獲取文件內容後,它會檢驗從Datanode獲取的數據跟相應的校驗和文件中的校驗和是否匹配,如果不匹配,客戶端可以選擇從其他Datanode獲取該數據塊的副本。
數據組織
數據塊
HDFS被設計成支持大文件,適用HDFS的是那些需要處理大規模的數據集的應用。這些應用都是隻寫入數據一次,但卻讀取一次或多次,並且讀取速度應能滿足流式讀取的需要。HDFS支持文件的“一次寫入多次讀取”語義。一個典型的數據塊大小是64MB。因而,HDFS中的文件總是按照64M被切分成不同的塊,每個塊儘可能地存儲於不同的Datanode中。
Staging
客戶端創建文件的請求其實並沒有立即發送給Namenode,事實上,在剛開始階段HDFS客戶端會先將文件數據緩存到本地的一個臨時文件。應用程序的寫操作被透明地重定向到這個臨時文件。當這個臨時文件累積的數據量超過一個數據塊的大小,客戶端纔會聯繫Namenode。Namenode將文件名插入文件系統的層次結構中,並且分配一個數據塊給它。然後返回Datanode的標識符和目標數據塊給客戶端。接着客戶端將這塊數據從本地臨時文件上傳到指定的Datanode上。當文件關閉時,在臨時文件中剩餘的沒有上傳的數據也會傳輸到指定的Datanode上。然後客戶端告訴Namenode文件已經關閉。此時Namenode纔將文件創建操作提交到日誌裏進行存儲。如果Namenode在文件關閉前宕機了,則該文件將丟失。
流水線複製
當客戶端向HDFS文件寫入數據的時候,一開始是寫到本地臨時文件中。假設該文件的副本系數設置爲3,當本地臨時文件累積到一個數據塊的大小時,客戶端會從Namenode獲取一個Datanode列表用於存放副本。然後客戶端開始向第一個Datanode傳輸數據,第一個Datanode一小部分一小部分(4 KB)地接收數據,將每一部分寫入本地倉庫,並同時傳輸該部分到列表中第二個Datanode節點。第二個Datanode也是這樣,一小部分一小部分地接收數據,寫入本地倉庫,並同時傳給第三個Datanode。最後,第三個Datanode接收數據並存儲在本地。因此,Datanode能流水線式地從前一個節點接收數據,並在同時轉發給下一個節點,數據以流水線的方式從前一個Datanode複製到下一個。
存儲空間回收
文件的刪除和恢復
當用戶或應用程序刪除某個文件時,HDFS會將這個文件重命名轉移到/trash目錄。只要文件還在/trash目錄中,該文件就可以被迅速地恢復。文件在/trash中保存的時間是可配置的,當超過這個時間時,Namenode就會將該文件從名字空間中刪除。刪除文件會使得該文件相關的數據塊被釋放。注意,從用戶刪除文件到HDFS空閒空間的增加之間會有一定時間的延遲。
減少副本系數
當一個文件的副本系數被減小後,Namenode會選擇過剩的副本刪除。下次心跳檢測時會將該信息傳遞給Datanode。Datanode遂即移除相應的數據塊,集羣中的空閒空間加大。同樣,在調用setReplication API結束和集羣中空閒空間增加間會有一定的延遲。
3.Hadoop分佈式文件系統使用指南
概述
HDFS是Hadoop應用用到的一個最主要的分佈式存儲系統。一個HDFS集羣主要由一個NameNode和很多個Datanode組成:Namenode管理文件系統的元數據,而Datanode存儲了實際的數據。
4.Hadoop Map/Reduce教程
概述
Hadoop Map/Reduce是一個使用簡易的軟件框架,基於它寫出來的應用程序能夠運行在由上千個商用機器組成的大型集羣上,並以一種可靠容錯的方式並行處理上T級別的數據集。
一個Map/Reduce 作業(job) 通常會把輸入的數據集切分爲若干獨立的數據塊,由 map任務(task)以完全並行的方式處理它們。
框架會對map的輸出先進行排序, 然後把結果輸入給reduce任務。通常作業的輸入和輸出都會被存儲在文件系統中。
整個框架負責任務的調度和監控,以及重新執行已經失敗的任務。
通常,Map/Reduce框架和分佈式文件系統是運行在一組相同的節點上的,也就是說,計算節點和存儲節點通常在一起。這種配置允許框架在那些已經存好數據的節點上高效地調度任務,這可以使整個集羣的網絡帶寬被非常高效地利用。
應用程序至少應該指明輸入/輸出的位置(路徑),並通過實現合適的接口或抽象類提供map和reduce函數。再加上其他作業的參數,就構成了作業配置(job configuration)。然後,Hadoop的 job client提交作業(jar包/可執行程序等)和配置信息給JobTracker,後者負責分發這些軟件和配置信息給slave、調度任務並監控它們的執行,同時提供狀態和診斷信息給job-client。
輸入與輸出
Map/Reduce框架運轉在<key, value>
鍵值對上,也就是說, 框架把作業的輸入看爲是一組<key, value>
鍵值對,同樣也產出一組 <key, value>
鍵值對做爲作業的輸出,這兩組鍵值對的類型可能不同。
一個Map/Reduce 作業的輸入和輸出類型如下所示:
(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)
強烈注意(看例子前必讀):
下面兩個例子是版本1.0.4時的例子,所以一些API與程序寫法會與現在的hadoop不同,你可以直接跳過這個兩個,看hadoop最新版本的WordCount例子,這對你來說沒有任何影響,我也推薦你這麼做。我之所以還留着這兩個“過時的例子”的原因,1.因爲我比較懶,不想刪代碼而已,哈哈哈。2.例子代碼後面的代碼解釋以及用法對於新的版本的例子同樣適用。
所以,我的建議是代碼看後面最新版本的,解釋再回頭來看舊版本的解讀。
例子:WordCount v1.0
在MapReduce中,當map生成的數據過大時,帶寬就成了瓶頸,怎樣精簡壓縮傳給Reduce的數據,有不影響最終的結果呢?
有一種方法就是使用Combiner,Combiner號稱本地的Reduce,Reduce最終的輸入,是Combiner的輸出。
第一個例子如下:
1. package org.myorg;
2.
3. import java.io.IOException;
4. import java.util.*;
5.
6. import org.apache.hadoop.fs.Path;
7. import org.apache.hadoop.conf.*;
8. import org.apache.hadoop.io.*;
9. import org.apache.hadoop.mapred.*;
10. import org.apache.hadoop.util.*;
11.
12. public class WordCount {
13.
14. public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {
15. private final static IntWritable one = new IntWritable(1);
16. private Text word = new Text();
17.
18. public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
19. String line = value.toString();
20. StringTokenizer tokenizer = new StringTokenizer(line);
21. while (tokenizer.hasMoreTokens()) {
22. word.set(tokenizer.nextToken());
23. output.collect(word, one);
24. }
25. }
26. }
27.
28. public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
29. public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
30. int sum = 0;
31. while (values.hasNext()) {
32. sum += values.next().get();
33. }
34. output.collect(key, new IntWritable(sum));
35. }
36. }
37.
38. public static void main(String[] args) throws Exception {
39. JobConf conf = new JobConf(WordCount.class);
40. conf.setJobName("wordcount");
41.
42. conf.setOutputKeyClass(Text.class);
43. conf.setOutputValueClass(IntWritable.class);
44.
45. conf.setMapperClass(Map.class);
46. conf.setCombinerClass(Reduce.class);
47. conf.setReducerClass(Reduce.class);
48.
49. conf.setInputFormat(TextInputFormat.class);
50. conf.setOutputFormat(TextOutputFormat.class);
51.
52. FileInputFormat.setInputPaths(conf, new Path(args[0]));
53. FileOutputFormat.setOutputPath(conf, new Path(args[1]));
54.
55. JobClient.runJob(conf);
57. }
58. }
59.
解釋:
Mapper(14-26行)中的map方法(18-25行)通過指定的 TextInputFormat(49行)一次處理一行。然後,它通過StringTokenizer 以空格爲分隔符將一行切分爲若干tokens,之後,輸出< , 1> 形式的鍵值對。
對於示例中的第一個輸入,map輸出是:
< Hello, 1>
< World, 1>
< Bye, 1>
< World, 1>
第二個輸入,map輸出是:
< Hello, 1>
< Hadoop, 1>
< Goodbye, 1>
< Hadoop, 1>
WordCount還指定了一個combiner (46行)。因此,每次map運行之後,會對輸出按照key進行排序,然後把輸出傳遞給本地的combiner(按照作業的配置與Reducer一樣),進行本地聚合。
第一個map的輸出是:
< Bye, 1>
< Hello, 1>
< World, 2>
第二個map的輸出是:
< Goodbye, 1>
< Hadoop, 2>
< Hello, 1>
Reducer(28-36行)中的reduce方法(29-35行) 僅是將每個key(本例中就是單詞)出現的次數求和。
因此這個作業的輸出就是:
< Bye, 1>
< Goodbye, 1>
< Hadoop, 2>
< Hello, 2>
< World, 2>
例子:WordCount v2.0
第二個例子如下:
1. package org.myorg;
2.
3. import java.io.*;
4. import java.util.*;
5.
6. import org.apache.hadoop.fs.Path;
7. import org.apache.hadoop.filecache.DistributedCache;
8. import org.apache.hadoop.conf.*;
9. import org.apache.hadoop.io.*;
10. import org.apache.hadoop.mapred.*;
11. import org.apache.hadoop.util.*;
12.
13. public class WordCount extends Configured implements Tool {
14.
15. public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {
16.
17. static enum Counters { INPUT_WORDS }
18.
19. private final static IntWritable one = new IntWritable(1);
20. private Text word = new Text();
21.
22. private boolean caseSensitive = true;
23. private Set<String> patternsToSkip = new HashSet<String>();
24.
25. private long numRecords = 0;
26. private String inputFile;
27.
28. public void configure(JobConf job) {
29. caseSensitive = job.getBoolean("wordcount.case.sensitive", true);
30. inputFile = job.get("map.input.file");
31.
32. if (job.getBoolean("wordcount.skip.patterns", false)) {
33. Path[] patternsFiles = new Path[0];
34. try {
35. patternsFiles = DistributedCache.getLocalCacheFiles(job);
36. } catch (IOException ioe) {
37. System.err.println("Caught exception while getting cached files: " + StringUtils.stringifyException(ioe));
38. }
39. for (Path patternsFile : patternsFiles) {
40. parseSkipFile(patternsFile);
41. }
42. }
43. }
44.
45. private void parseSkipFile(Path patternsFile) {
46. try {
47. BufferedReader fis = new BufferedReader(new FileReader(patternsFile.toString()));
48. String pattern = null;
49. while ((pattern = fis.readLine()) != null) {
50. patternsToSkip.add(pattern);
51. }
52. } catch (IOException ioe) {
53. System.err.println("Caught exception while parsing the cached file '" + patternsFile + "' : " + StringUtils.stringifyException(ioe));
54. }
55. }
56.
57. public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
58. String line = (caseSensitive) ? value.toString() : value.toString().toLowerCase();
59.
60. for (String pattern : patternsToSkip) {
61. line = line.replaceAll(pattern, "");
62. }
63.
64. StringTokenizer tokenizer = new StringTokenizer(line);
65. while (tokenizer.hasMoreTokens()) {
66. word.set(tokenizer.nextToken());
67. output.collect(word, one);
68. reporter.incrCounter(Counters.INPUT_WORDS, 1);
69. }
70.
71. if ((++numRecords % 100) == 0) {
72. reporter.setStatus("Finished processing " + numRecords + " records " + "from the input file: " + inputFile);
73. }
74. }
75. }
76.
77. public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
78. public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
79. int sum = 0;
80. while (values.hasNext()) {
81. sum += values.next().get();
82. }
83. output.collect(key, new IntWritable(sum));
84. }
85. }
86.
87. public int run(String[] args) throws Exception {
88. JobConf conf = new JobConf(getConf(), WordCount.class);
89. conf.setJobName("wordcount");
90.
91. conf.setOutputKeyClass(Text.class);
92. conf.setOutputValueClass(IntWritable.class);
93.
94. conf.setMapperClass(Map.class);
95. conf.setCombinerClass(Reduce.class);
96. conf.setReducerClass(Reduce.class);
97.
98. conf.setInputFormat(TextInputFormat.class);
99. conf.setOutputFormat(TextOutputFormat.class);
100.
101. List<String> other_args = new ArrayList<String>();
102. for (int i=0; i < args.length; ++i) {
103. if ("-skip".equals(args[i])) {
104. DistributedCache.addCacheFile(new Path(args[++i]).toUri(), conf);
105. conf.setBoolean("wordcount.skip.patterns", true);
106. } else {
107. other_args.add(args[i]);
108. }
109. }
110.
111. FileInputFormat.setInputPaths(conf, new Path(other_args.get(0)));
112. FileOutputFormat.setOutputPath(conf, new Path(other_args.get(1)));
113.
114. JobClient.runJob(conf);
115. return 0;
116. }
117.
118. public static void main(String[] args) throws Exception {
119. int res = ToolRunner.run(new Configuration(), new WordCount(), args);
120. System.exit(res);
121. }
122. }
123.
運行樣例
輸入樣例:
$ bin/hadoop dfs -ls /usr/joe/wordcount/input/
/usr/joe/wordcount/input/file01
/usr/joe/wordcount/input/file02
$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file01
Hello World, Bye World!
$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file02
Hello Hadoop, Goodbye to hadoop.
運行程序:
$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount /usr/joe/wordcount/input /usr/joe/wordcount/output
輸出:
$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000
Bye 1
Goodbye 1
Hadoop, 1
Hello 2
World! 1
World, 1
hadoop. 1
to 1
現在通過DistributedCache插入一個模式文件,文件中保存了要被忽略的單詞模式。
$ hadoop dfs -cat /user/joe/wordcount/patterns.txt
.
,
!
to
再運行一次,這次使用更多的選項:
$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount -Dwordcount.case.sensitive=true /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt
應該得到這樣的輸出:
$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000
Bye 1
Goodbye 1
Hadoop 1
Hello 2
World 2
hadoop 1
再運行一次,這一次關閉大小寫敏感性(case-sensitivity):
$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount -Dwordcount.case.sensitive=false /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt
輸出:
$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000
bye 1
goodbye 1
hadoop 2
hello 2
world 2
程序要點:
通過使用一些Map/Reduce框架提供的功能,WordCount的第二個版本在原始版本基礎上有了如下的改進:
1.展示了應用程序如何在Mapper (和Reducer)中通過configure方法 修改配置參數(28-43行)。
2.展示了作業如何使用DistributedCache 來分發只讀數據。 這裏允許用戶指定單詞的模式,在計數時忽略那些符合模式的單詞(104行)。
3.展示Tool接口和GenericOptionsParser處理Hadoop命令行選項的功能 (87-116, 119行)。
4.展示了應用程序如何使用Counters(68行),如何通過傳遞給map(和reduce) 方法的Reporter實例來設置應用程序的狀態信息(72行)。
Hadoop Streaming
Hadoop streaming是Hadoop的一個工具, 它幫助用戶創建和運行一類特殊的map/reduce作業, 這些特殊的map/reduce作業是由一些可執行文件或腳本文件充當mapper或者reducer。
最新版本的例子:
Example: WordCount v1.0
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCount {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
解釋與用法同上。
Example: WordCount v2.0
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.StringUtils;
public class WordCount2 {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
static enum CountersEnum { INPUT_WORDS }
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
private boolean caseSensitive;
private Set<String> patternsToSkip = new HashSet<String>();
private Configuration conf;
private BufferedReader fis;
@Override
public void setup(Context context) throws IOException,
InterruptedException {
conf = context.getConfiguration();
caseSensitive = conf.getBoolean("wordcount.case.sensitive", true);
if (conf.getBoolean("wordcount.skip.patterns", true)) {
URI[] patternsURIs = Job.getInstance(conf).getCacheFiles();
for (URI patternsURI : patternsURIs) {
Path patternsPath = new Path(patternsURI.getPath());
String patternsFileName = patternsPath.getName().toString();
parseSkipFile(patternsFileName);
}
}
}
private void parseSkipFile(String fileName) {
try {
fis = new BufferedReader(new FileReader(fileName));
String pattern = null;
while ((pattern = fis.readLine()) != null) {
patternsToSkip.add(pattern);
}
} catch (IOException ioe) {
System.err.println("Caught exception while parsing the cached file '"
+ StringUtils.stringifyException(ioe));
}
}
@Override
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
String line = (caseSensitive) ?
value.toString() : value.toString().toLowerCase();
for (String pattern : patternsToSkip) {
line = line.replaceAll(pattern, "");
}
StringTokenizer itr = new StringTokenizer(line);
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
Counter counter = context.getCounter(CountersEnum.class.getName(),
CountersEnum.INPUT_WORDS.toString());
counter.increment(1);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
GenericOptionsParser optionParser = new GenericOptionsParser(conf, args);
String[] remainingArgs = optionParser.getRemainingArgs();
if (!(remainingArgs.length != 2 | | remainingArgs.length != 4)) {
System.err.println("Usage: wordcount <in> <out> [-skip skipPatternFile]");
System.exit(2);
}
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount2.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
List<String> otherArgs = new ArrayList<String>();
for (int i=0; i < remainingArgs.length; ++i) {
if ("-skip".equals(remainingArgs[i])) {
job.addCacheFile(new Path(remainingArgs[++i]).toUri());
job.getConfiguration().setBoolean("wordcount.skip.patterns", true);
} else {
otherArgs.add(remainingArgs[i]);
}
}
FileInputFormat.addInputPath(job, new Path(otherArgs.get(0)));
FileOutputFormat.setOutputPath(job, new Path(otherArgs.get(1)));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
解釋與用法同上。