1. 程式人生 > >MapReduce之WordCount程式詳解及常見錯誤彙總

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,然後位置也要與我們配置的位置一致,這樣再執行的時候就不會報錯了