1. 程式人生 > >Intellij IDEA開發Hadoop偽分散式應用

Intellij IDEA開發Hadoop偽分散式應用

在上一篇部落格中(https://blog.csdn.net/qq_33588730/article/details/81123614)安裝CDH5.15.0之後,安裝好的是單機版Hadoop,並且準備好了偽分散式與全分散式需要的ssh遠端登入配置,現在試試將Hadoop以偽分散式方式來執行,並用流行的Java開發軟體Intellij IDEA來學習和開發Hadoop應用。

一、將Hadoop(CDH)以偽分散式啟動

首先需要修改一些配置檔案,在命令列中或者檔案管理器的圖形介面中找到Hadoop(CDH)安裝目錄下的/etc/hadoop目錄,用vim或者gedit編輯器編輯以下4個檔案:

(1)core-site.xml,顧名思義,用於定義一些核心的通用配置(如果要返回單機模式,刪除該檔案裡的配置),定義HDFS的namenode主機與埠,預設為8020埠執行namenode:

(2)hdfs-site.xml,用於配置HDFS的相關屬性,這裡配置儲存檔案副本數,學習的時候只有本機所以副本數為1:

(3)mapred-site.xml,找到Hadoop(CDH)安裝目錄下/etc/hadoop資料夾內的mapred-site.xml.template檔案,並重新命名刪去後綴.template,有該字尾就是模板檔案不會被啟用,去掉字尾變成mapred-site.xml可以使該配置檔案生效,這個檔案是用於配置MapReduce相關屬性的,進行編輯,讓mapreduce執行在yarn框架下:

這裡需要注意的是,如果不需要啟動YARN的時候,一定要把mapred-site.xml

重新命名為mapred-site.xml.template模板檔案,否則在該配置檔案存在,而未開啟 YARN 的情況下,執行程式會提示 “Retrying connect to server: 0.0.0.0/0.0.0.0:8032” 的錯誤,這也是為何預設安裝單機模式的時候,該配置檔案原本名為 mapred-site.xml.template。

(4)yarn-site.xml,用於配置YARN的相關屬性,這裡配置yarn的resourcemanager的主機名,以及nodemanager上執行的附屬服務,配置成mapreduce_shuffle可執行mapreduce:

配置好配置檔案之後,用hdfs namenode -format

命令格式化HDFS檔案系統,命令列日誌的末尾會顯示“successfully formatted”和“Exiting with status 0”,這就表示初始化成功,如果返回的狀態碼是1則有錯誤(這裡直接可以在/home/yyc目錄下輸入命令啟動在/opt目錄下的hdfs是因為上一篇部落格配置了環境變數):

需要注意的是,以後每次開機後,在啟動Hadoop前都要使用這個命令格式化HDFS,因為HDFS實際的檔案預設路徑在系統的/tmp/hadoop-使用者名稱/dfs/目錄下,下次開機的時候會自動清除/tmp目錄下的快取,因此每次都需要重新初始化把檔案寫入/tmp,否則直接使用start-all.sh命令啟動hdfs的時候會導致NameNode無法啟動,使用jps命令檢視程序的時候會發現沒有NameNode,從而在執行hadoop fs -ls /命令的時候被拒絕連線,並報錯“ls: Call From localhost/127.0.0.1 to localhost:8020 failed on connection exception: java.net.ConnectException: Connection refused”。

當然為了避免每次初始化NameNode的麻煩,也可以在用hdfs namenode -format命令初始化之前,在hdfs-site.xml檔案中新增如下程式碼,使NameNode和DataNode的資料儲存路徑更換為其他的,就可以不用每次初始化NameNode了(預設NameNode的HDFS元資料在/tmp/hadoop-使用者名稱/dfs/name目錄,DataNode儲存的資料塊在/tmp/hadoop-使用者名稱/dfs/data目錄,SecondaryNameNode目錄在/tmp/hadoop-使用者名稱/dfs/namesecondary下):

<!--hdfs中namenode在linux下的儲存路徑-->
    <property>
          <name>dfs.name.dir</name>
          <value>/opt/hadoop-2.6.0-cdh5.15.0/tmp/dfs/name</value>
    </property>
<!--hdfs中datanode在linux下的儲存路徑-->
    <property>
          <name>dfs.data.dir</name>
          <value>/opt/hadoop-2.6.0-cdh5.15.0/tmp/dfs/data</value>
    </property>

或者:

<!--hdfs中namenode和danode儲存路徑,若是設定此引數上面兩個引數就不用設定-->
    <property>
          <name>dfs.tmp.dir</name>
          <value>/opt/hadoop-2.6.0-cdh5.15.0/tmp</value>
    </property>

配置好hdfs-site.xml後,在core-site.xml中加入以下程式碼,修改Hadoop的臨時目錄位置,對應hdfs-site.xml中的NameNode和DataNode路徑,是它們所在目錄的的父目錄:

<property>
       <name>hadoop.tmp.dir</name>
       <value>/opt/hadoop-2.6.0-cdh5.15.0/tmp</value>
</property>

除了編輯Hadoop配置檔案的方法,也可以使用cd /etc/default命令進入到rcS檔案所在目錄,然後用sudo vim rcS命令編輯該檔案,可以看到有一行“TMPTIME=0”,意思是下次啟動立刻刪除/tmp下的快取,可以改為正整數變為x天后刪除,改為-1則為永遠不刪除快取:

在用hdfs namenode -format命令將HDFS格式化之後,利用start-all.sh(相當於start-dfs.shstart-yarn.sh)啟動hdfs和yarn等元件,但是報錯說沒有指定JAVA_HOME環境變數,然而用echo $JAVA_HOME命令是可以返回Java所在目錄的,這個環境變數其實存在,非常奇怪:

於是找到Hadoop安裝目錄下的/etc/hadoop目錄,編輯裡面的hadoop-env.sh,將裡面的JAVA_HOME指向安裝Java所在的絕對路徑:

然後再試試start-all.sh命令,以及mr-jobhistory-daemon.sh start historyserver命令,這樣就可以成功啟動偽分散式Hadoop:

上面的步驟會使本機啟動以下守護程序:一個namenode,一個輔助namenode,一個datanode(HDFS),一個資源管理器,一個節點管理器(YARN)和一個歷史伺服器(MapReduce)。可以看到Hadoop安裝目錄下的logs目錄裡生成了一些日誌檔案,可以用於檢查守護程序是否成功啟動,如下所示:

或者,通過Web介面:在瀏覽器位址列裡輸入http://localhost:50070/檢視NameNode,在http://localhost:8008/檢視resource manager,在http://localhost:19888/檢視歷史伺服器,如下所示:

此外,用Java的jps命令也可以檢視守護程序是否正在執行,如下所示:

其中NameNode、SecondaryNameNode和DataNode屬於HDFS,ResourceManager和NodeManager屬於YARN,要終止守護程序,則要反過來輸入三個停止命令(後兩條也可以用stop-all.sh代替),如下所示:

mr-jobhistory-daemon.sh stop historyserver
stop-yarn.sh
stop-dfs.sh

啟動偽分散式“叢集”之後,用hadoop fs -ls /命令可以檢視HDFS中存在的檔案目錄,但是發現會報警告,顯示“WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable”,意思是hadoop不能載入{Hadoop安裝目錄}/lib/native裡的本地庫,警告不消除也能正常執行,但是略微拖慢執行速度,解決方法是Ubuntu系統的/etc/profile檔案以及CDH安裝目錄下的/etc/hadoop/hadoop-env.sh檔案兩處都加入三個環境變數

export HADOOP_HOME=/opt/hadoop-2.6.0-cdh5.15.0
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib:$HADOOP_COMMON_LIB_NATIVE_DIR"

然後再試試hadoop fs -ls /命令,就沒有WARNING了,如下所示:

接著可以用hadoop fs -mkdir /input命令在HDFS上新增一個輸入檔案的目錄,我在解決上面WARNING問題之前已經新增過,所以上圖會顯示有/input資料夾。

二、配置Intellij IDEA

偽分散式Hadoop啟動之後,開啟Intellij IDEA,新建一個工程,如下所示:

然後選擇建立Maven工程,這是一個用於管理jar包的元件,本來一些Java程式需要依賴各種各樣的jar包,需要自己去手動找到路徑去匯入,依賴大量jar包的時候手動一個個匯入太過麻煩,Maven可以通過編輯一個叫pom.xml的配置檔案來從Maven的程式碼庫中自動下載所需要的jar包並配置依賴:

點選Next後,工程組名稱和工程名稱都可以隨便寫,然後點選Next,如下所示:

然後設定工程名稱和所在目錄,這裡的Project name最好和ArtifactId一樣,然後點選finish,如下所示:

建立工程後,在左側工程目錄選單裡找到pom.xml,如下所示:

對這個檔案進行編輯,根據Cloudera的官方網址的指導(https://www.cloudera.com/documentation/enterprise/release-notes/topics/cdh_vd_cdh5_maven_repo.html)輸入以下xml程式碼:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cloudera.hadoop</groupId>
    <artifactId>bigdata</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <properties>
        <cdh.version>2.6.0-cdh5.15.0</cdh.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${cdh.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-core</artifactId>
            <version>2.6.0-mr1-cdh5.15.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>${cdh.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.6.0-mr1-cdh5.15.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

pom.xml的dependency標籤下可以加入想要匯入的jar依賴包,groupId是工程組名稱,artifactId是工程名稱,version是版本,這三個資訊唯一確定了要下載的jar包。

然後在左側工程目錄選單欄裡右鍵src/main/java資料夾,新建一個包(Package),如下所示:

並在建立的wordcount2包下新建一個WordCount2類,寫入如下程式碼:

package wordcount2;

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.util.GenericOptionsParser;

import java.io.IOException;
import java.util.StringTokenizer;

public class WordCount2 {
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{
        private final static IntWritable one = new IntWritable(1);
        private Text word = new Text();

        @Override
        protected 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();

        @Override
        protected 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 IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();

        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();

        if(otherArgs.length != 2){
            System.err.println("Usage: wordcount <in> <out>");
            System.exit(2);
        }

        Job job = new Job(conf, "word count2");
        job.setJarByClass(WordCount2.class);
        job.setMapperClass(TokenizerMapper.class);
        job.setReducerClass(IntSumReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));

        boolean flag = job.waitForCompletion(true);
        System.out.println("Succeed! " + flag);
        System.exit(flag ? 0 : 1);
        System.out.println();
    }
}

然後點選上面選單欄的Run,點選Edit Configurations(在用命令列執行hadoop jar命令的時候,新增Application配置可跳過,該步驟是為了在Intellij IDEA中執行Hadoop除錯),如下所示:

進入工程配置介面,點選左上角的加號,新增Application配置,如下所示:

然後在右側,Name可以給這個配置起名,Main class用於設定執行時的主類,格式為“Package.Class”,在Program arguments欄裡輸入Hadoop任務的輸入目錄和輸出目錄,我這裡輸入的是偽分散式情況下輸入和輸出資料夾的URL,這兩個目錄地址要與core-site.xml配置檔案中的fs.defaultFS屬性對應,如下所示:

接著將Hadoop的配置檔案放到工程目錄下的src/main/resources資料夾下,如下所示:

然後就可以把寫的Java程式碼編譯成jar包,點選介面右側的Maven Projects選項卡,再雙擊Lifecycle選項下的compile,執行完成後再雙擊package在左側工程目錄裡生成一個jar包,或者可以在介面底部開啟terminal執行mvn compile和mvn package命令也是同樣的效果,如下所示:

然後把生成的jar包重新命名為wordcount2.jar,複製到讓自己方便的路徑,我複製到了自己手動掛載的機械硬碟分割槽/mnt/sda6上。

接著,將Hadoop安裝目錄下的/etc/hadoop目錄內所有xml檔案都作為輸入檔案上傳到HDFS的/input資料夾內,輸入如下命令:

hadoop fs -put /opt/hadoop-2.6.0-cdh5.15.0/etc/hadoop/*.xml /input

上傳完成後,用hadoop fs -ls /input命令檢視剛才上傳的xml檔案,如下所示:

然後利用自己之前生成的jar包執行Hadoop任務,並從HDFS上的/input資料夾內讀取輸入檔案進行處理,處理結果輸出到/output資料夾。輸入如下命令:

hadoop jar /mnt/sda6/wordcount2.jar wordcount2.WordCount2 /input /output

指定生成的jar包所在位置後,還要指定執行的是哪個包中的哪個類,以"Package.class"的格式寫清楚,後面加上用於輸入給任務的檔案所在路徑,以及處理任務完成後輸出的結果檔案所在路徑。

就可以看到任務執行成功,如下所示:

三、處理輸出結果

在Hadoop任務執行成功後,可以通過hadoop fs -ls /命令看到HDFS生成了/output資料夾,裡面有輸出結果檔案,如下所示:

可以通過hadoop fs -cat /output/part-r-00000命令檢視輸出結果檔案,如下所示:

如果需要把Hadoop處理結果從HDFS下載到本地,可以使用hadoop fs -get /output/part-r-00000 /mnt/sda6命令將結果檔案下載到自己指定的目錄下,如下所示:

當然,也可以通過圖形介面來下載,在瀏覽器位址列裡輸入http://localhost:50070並回車,在頂部選單欄點選Utilities,下拉選單裡點選Browse the file system,如下所示:

進入Browse Directory介面後,點選右側的output連結,或者在上面搜尋欄輸入/output並點選“Go!”,就可以進入/output目錄,如下所示:

進入/output目錄的介面後,點選右側的part-r-00000連結,就會開啟一個對話方塊,顯示該檔案的基本資訊,上面有Download按鈕可以下載該檔案,如下所示:

所有的結果分析工作處理完之後,一定要記得用hadoop fs –rm –r /output命令刪除/output目錄及其包含的所有檔案,否則執行下一個任務的時候,還存在輸出目錄會報錯:“org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://localhost/output already exists”。如果之前沒有修改NameNode和DataNode等HDFS元件的儲存路徑配置,第二次開機的時候也會自動刪除/tmp/hadoop-使用者名稱/下所有資料,包括輸出資料夾也會被清除。