MapReduce之WordCount程式詳解及常見錯誤彙總
前言:
在之前的筆記中,我們已經成功的關聯了eclipse和hadoop,對FileSystem的使用進行了簡單瞭解。
下面就是Hadoop中的重點MapReduce程式的開發。作為MapReduce(以下使用MR來代替)開發中的入門程式WordCount,基本是每個學習MapReduce的同學都必知必會的。有關於WordCount的概念筆者就不再贅述,網上有N多文章講解。
本次部落格主要是記錄筆者在Windows環境下使用eclipse進行WordCount程式編寫過程中所遇到的問題及解決方案。
準備工作:
* 建立maven專案,命名為hadoop,將Linux環境下hadoop的配置檔案core-site.xml/mapred-site.xml/hdfs-site.xml/yarn-site.xml放入hadoop/src/main/resources中(主要是因為MR程式需要載入這些配置檔案中的配置內容
* 在hadoop/src/main/resources中建立log4j.properties檔案,內容如下
log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
注意:之所以要建立該檔案,是因為在eclipse中啟動MR程式時,預設是沒有日誌的,我們載入log4j的配置後,root設定為DEBUG級別,那麼程式的每一步操作我們都可以通過日誌來觀察到,有利於我們定位問題
* 使用使用者hxw(筆者)來啟動hadoop
HADOOP_HOME/sbin/start-dfs.sh
HADOOP_HOME/sbin/start-yarn.sh
1.WordCount程式的編寫
具體內容如下,筆者不再詳述
package hadoop.mr; import java.io.IOException; import java.net.URI; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; 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.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCount { public static class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable>{ private final static IntWritable ONE = new IntWritable(1); private Text word = new Text(); /** * map程式,進行切割轉換 */ @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException { // 1.解析value為token,預設會按照空格進行分割 StringTokenizer token = new StringTokenizer(value.toString()); while(token.hasMoreTokens()){ // 2.將分割後的字元放入Word word.set(token.nextToken()); // 3.輸出k-v格式 類似(hadoop,1) context.write(word, ONE); } } } public static class WCReduce extends Reducer<Text, IntWritable, Text, IntWritable>{ private IntWritable result = new IntWritable(); /** * reduce程式,對map的結果進行合併 */ @Override protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException { // 1.計算總和 int sum = 0; for (IntWritable intWritable : values) { sum += intWritable.get(); } result.set(sum); // 2.輸出結果 context.write(key, result); } } private static String INPUT_PATH = "/user/hadoop/mapreduce/input/"; private static String OUTPUT_PATH = "/user/hadoop/mapreduce/output/"; private static String HDFS_URI = "hdfs://hadoop:9000";// 對應於core-site.xml中的FS.default public static void main(String[] args) { try { // 1.如果已經有output_path,則先進行刪除 deleteOutputFile(OUTPUT_PATH); // 2.建立job,設定基本屬性 Job job = Job.getInstance(); job.setJarByClass(WordCount.class); job.setJobName("wordcount"); // 3.設定Mapper、Reducer job.setMapperClass(WCMapper.class); job.setReducerClass(WCReduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 4.設定輸入路徑和輸出路徑 FileInputFormat.addInputPath(job, new Path(INPUT_PATH)); FileOutputFormat.setOutputPath(job, new Path(OUTPUT_PATH)); // 5.執行,執行完成後退出程式 System.exit(job.waitForCompletion(true) ? 0 : 1); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } static void deleteOutputFile(String path) throws Exception{ Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new URI(HDFS_URI),conf,"hxw"); if(fs.exists(new Path(path))){ fs.delete(new Path(path),true); } } }
2.遇到的問題彙總
1)訪問HDFS無許可權
報錯內容一般如下:
org.apache.hadoop.security.AccessControlException: Permission denied: user=root, access=WRITE, inode="":nutch:supergroup:rwxr-xr-x
報錯原因:主要是由於HDFS的檔案系統都是有使用者和許可權的,如果當前使用者無許可權則在使用該檔案或資料夾的時候會報錯。
解決方案:
* 使用hdfs dfs -chmod 命令來修改相關檔案或資料夾許可權;
* 如果在測試環境,使用者不想這麼麻煩來修改許可權的話,也可使用配置來禁用hdfs的許可權管理,可以在hdfs-site.xml中配置以下內容
<property>
<name>dfs.permissions</name>
<value>false</value>
</property>
筆者在執行的過程中,比較煩惱的一件事就是,執行的時候沒有任何報錯,任務排程介面中也沒有任務顯示
將專案打成jar包放入Linux環境下,也是可以執行的,很奇怪。
後來,就添加了log4j.properties檔案,將root設定為DEBUG級別(內容如上所示),就看到了其中的報錯。
所以,我們在執行專案的時候,一定要新增日誌檔案
3)建立對ResourceManager連線的時候報錯
DEBUG [org.apache.hadoop.ipc.Client] - closing ipc connection to 0.0.0.0/0.0.0.0:8032: Connection refused: no further information
java.net.ConnectException: Connection refused: no further information
報錯原因:看報錯資訊,我們知道是在建立對0.0.0.0:8032的Connection時候失敗。為什麼會失敗?應該是無法連線到0.0.0.0這個IP。我們沒有在配置檔案中配置這個IP和埠,那麼這個應該是預設配置。我們去hadoop官網的core-default.xml、yarn-default.xml等預設配置檔案進行檢視的時候,發現在yarn-site.xml中發現以下內容
yarn.resourcemanager.hostname 0.0.0.0 The hostname of the RM.
yarn.resourcemanager.address ${yarn.resourcemanager.hostname}:8032
那麼可以確定這個IP:port是對ResourceManager的連線失敗
我們知道ResourceManager負責叢集的資源分配,所有NodeManager都需要與ResourceManager進行通訊交換資訊,yarn.resourcemanager.hostname預設為0.0.0.0,我們將這個內容修改為hadoop,對應著當前本機地址即可
解決方案:在yarn-site.xml中新增
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop</value>
</property>
4)no job control
報錯資訊如下所示:
Exception message: /bin/bash: line 0: fg: no job control
Stack trace: ExitCodeException exitCode=1: /bin/bash: line 0: fg: no job control
at org.apache.hadoop.util.Shell.runCommand(Shell.java:545)
at org.apache.hadoop.util.Shell.run(Shell.java:456)
...
報錯原因:由於我們使用Windows平臺進行開發並新增MR任務,而hadoop部署在Linux平臺上,故針對跨平臺的job會報該錯
解決方案:在mapred-site.xml中新增以下配置
設定為job提交允許跨平臺
<property>
<name>mapreduce.app-submission.cross-platform</name>
<value>true</value>
</property>
5)ClassNotFoundException
Error: java.lang.RuntimeException: java.lang.ClassNotFoundException: Class hadoop.mr.WordCount$WCMapper not found
at org.apache.hadoop.conf.Configuration.getClass(Configuration.java:2195)
at org.apache.hadoop.mapreduce.task.JobContextImpl.getMapperClass(JobContextImpl.java:186)
...
解決方案:在core-site.xml中設定如下配置:
<property>
<name>mapred.jar</name>
<value>C:/Users/lucky/Desktop/wc.jar</value>
</property>
然後每次執行WordCount任務的時候,先將當前專案匯出為一個jar包,命名為wc.jar,然後位置也要與我們配置的位置一致,這樣再執行的時候就不會報錯了