---
# System prepended metadata

title: 'Hadoop Ecosystem (HDFS, MapReduce)'
tags: [Course]

---

# Hadoop Ecosystem (HDFS, MapReduce)

###### tags: `Course`

## Hadoop 必須套件
以下套件為本次安裝過程中會使用到的部分 :
* [Ubuntu 18.04 LTS (64-bits)](https://www.ubuntu.com/download/desktop/thank-you?version=18.04.2&architecture=amd64) 
* [Hadoop 3.2.0](https://www.apache.org/dist/hadoop/core/hadoop-3.2.0/hadoop-3.2.0.tar.gz)
* [Zookeeper 3.4.14](http://apache.stu.edu.tw/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz)
* [Java 8 oracle](https://openjdk.java.net/install/)

## Hadoop 安裝前所需注意的事項
1. 此處會跳過 ubuntu 16.04 與虛擬機的建置。
2. 每台虛擬機上的使用者名稱請統一，例如 : **hadoop** 。
3. 本次安裝過程會建立四台虛擬機，`Master` 、 `Datanode1` 、 `Datanode2` 、 `Datanode3` 。
4. 各虛擬機的 IP 需在同網域且都不相同，可參考虛擬機設定的 **橋接介面卡** 。
5. 每個步驟都會先說明是==所有主機==都要執行同步驟，還是只有==Master 主機==需要做而已。

## Hadoop Fully-Distribted Operation
本安裝過程皆以 fully-distributed operation 為主，如果要 standalone operation 或 pseudo-distributed operation 請參考 [此處](https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/SingleCluster.html) 。

### Step 1
==所有主機==
* 更新所有主機的套件。
```
$ sudo apt-get update
```
* 安裝 `vim` 或其他文字編輯器。
```
$ sudo apt-get install vim
```
* 集群、單節點模式都需要用到 ssh 登陸（類似於遠程登陸，你可以登錄某台 Linux 主機，並且在上面運行命令）， Ubuntu 默認已安裝了 ssh client ，此外還需要安裝 ssh server 。
```
$ sudo apt-get install openssh-server
```
* 安裝完後，應該可以透過 `$ ssh localhost` 連到本機，但需要輸入密碼。
* 退出 ssh 後使用 `ssh-keygen` 產生各節點的公、私鑰。
```
$ cd ~/.ssh/
$ ssh-keygen -t rsa
$ cat ./id_rsa.pub >> ./authorized_keys
```
* 更新後，使用 `$ ssh localhost` 就可以無密碼登入了。
* 修改各主機的 host name ， Master 虛擬機叫 `Master` ， Datanode1 虛擬機叫 `Datanode1` ，以此類推。
```
$ sudo vim /etc/hostname
```
* 修改各 `/etc/hosts` 文件中映射到其他節點的 ip 。
```
$ sudo vim /etc/hosts
```
* 修改完應該如下 :
```
127.0.0.1       localhost
10.1.1.13       Master
10.1.1.14       Datanode1
10.1.1.15       Datanode2
10.1.1.12       Datanode3
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
```
* 如果有多餘的對應，例如 127.0.0.1 對到 Master 等，應該刪除。
* `Datanode1` 、 `Datanode2` 、 `Datanode3` 、 `Master` 都要修改 `/etc/hosts` 。
* 修改後，對各節點使用 `ping` 應該都要有回應，否則失敗。
### Step 2
==Master 主機==
* 需要讓 `Master` 可以不用密碼的從 ssh 登入，故將 `Master` 的公鑰傳至其他節點 (`Datanode1` 、 `Datanode2` 、 `Datanode3` 都要)。
```
$ scp /home/hadoop/.ssh/id_rsa.pub hadoop@Datanode1:/home/hadoop
$ scp /home/hadoop/.ssh/id_rsa.pub hadoop@Datanode2:/home/hadoop
$ scp /home/hadoop/.ssh/id_rsa.pub hadoop@Datanode3:/home/hadoop
```
* 接著登入 `Datanode1` 並將公鑰加入 `authorized_keys` ，其他節點照做。
```
$ mkdir ~/.ssh
$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
$ rm ~/id_rsa.pub
```
* 如此一來， `Master` 就可以無密碼登入至其他節點。
### Step 3
==所有主機==
*  安裝 Java 環境， Java 環境可選擇 Oracle 的 JDK ，或是 OpenJDK 。為圖方便，這邊直接通過命令安裝 OpenJDK 8。
```
$ sudo apt-get install openjdk-8-jre openjdk-8-jdk
```
* 在 **環境變數** 文件 `~/.bashrc` 的第一列加入下列變數。
```
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
```
* 執行 `$ source ~/.bashrc` 來使環境變數生效。
* 可透過下列命令來確認執行是否正確。
```
$ echo $JAVA_HOME
/usr/lib/jvm/java-8-openjdk-amd64

$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

$ $JAVA_HOME/bin/java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
```
* 如果以上命令的結果都有正確顯示則表示環境變數沒問題。
### Step 4
==Master 主機==
* 這裡選擇將 Hadoop 安裝至 `/usr/local/` ，先下載 Hadoop 3.2.0 到 `~/Downloads` 。
* 解壓縮至 `/usr/local/` 後再更改 owner 。
```
$ sudo tar -zxf ~/Downloads/hadoop-3.2.0.tar.gz -C /usr/local
$ cd /usr/local
$ sudo mv ./hadoop-3.2.0 ./hadoop
$ sudo chown -R hadoop ./hadoop
```
* 可輸入以下命令來檢查 Hadoop 使否可用 :
```
$ cd /usr/local/hadoop
$ ./bin/hadoop version
Hadoop 3.2.0
Source code repository https://github.com/apache/hadoop.git -r e97acb3bd8f3befd27418996fa5d4b50bf2e17bf
Compiled by sunilg on 2019-01-08T06:08Z
Compiled with protoc 2.5.0
From source with checksum d3f0795ed0d9dc378e2c785d3668f39
This command was run using /usr/local/hadoop/share/hadoop/common/hadoop-common-3.2.0.jar
```
* 如果有出現上述訊息，表示安裝成功。
* 在集群/分散式模式下，會修改在 `/usr/local/hadoop/etc/hadoop` 路徑下的五個配置文件 : `workers` 、 `core-site.xml` 、 `hdfs-site.xml` 、 `mapred-site.xml` 、 `yarn-site.xml` 。
    1. 在 `workers` 加入所有 DataNode 的 host name : 
    
        ```
        Datanode1
        Datanode2
        Datanode3
        ```
        :::info
        如果 NameNode 本身也是 DataNode ，也需要在此加入 `Master` 。
        :::
    2. 對 `core-site.xml` 加入以下配置 : 
        ```
        <configuration>
            <property>
                    <name>fs.defaultFS</name>
                    <value>hdfs://Master:9000</value>
            </property>
            <property>
                    <name>hadoop.tmp.dir</name>
                    <value>file:/usr/local/hadoop/tmp</value>
                    <description>Abase for other temporary directories.</description>
            </property>
        </configuration>
        ```
        :::info
        `fs.defaultFS` 是預設的 NameNode 的 URL ，在此為 `Master` 。
        :::
    3. 對 `hdfs-site.xml` 加入以下配置 : 
        ```
        <configuration>
                <property>
                        <name>dfs.namenode.secondary.http-address</name>
                        <value>Master:50090</value>
                </property>
                <property>
                        <name>dfs.replication</name>
                        <value>3</value>
                </property>
                <property>
                        <name>dfs.namenode.name.dir</name>
                        <value>file:/usr/local/hadoop/tmp/dfs/name</value>
                </property>
                <property>
                        <name>dfs.datanode.data.dir</name>
                        <value>file:/usr/local/hadoop/tmp/dfs/data</value>
                </property>
        </configuration>
        ```
        :::info
        `dfs.replication` 意指有多少的 DataNode 節點。
        :::
    4. 對 `mapred-site.xml` 加入以下配置 :
        ```
        <configuration>
                <property>
                        <name>mapreduce.framework.name</name>
                        <value>yarn</value>
                </property>
                <property>
                        <name>yarn.app.mapreduce.am.env</name>
                        <value>HADOOP_MAPRED_HOME=$HADOOP_MAPRED_HOME</value>
                </property>
                <property>
                        <name>mapreduce.map.env</name>
                        <value>HADOOP_MAPRED_HOME=$HADOOP_MAPRED_HOME</value>
                </property>
                <property>
                        <name>mapreduce.reduce.env</name>
                        <value>HADOOP_MAPRED_HOME=$HADOOP_MAPRED_HOME</value>
                </property>
        </configuration>
        ```
        :::info
        `HADOOP_MAPRED_HOME` 為 hadoop 的目錄，如果不設定會無法執行 MapReduce 應用，稍後會加入此環境變數。
        :::
    5. 對 `yarn-site.xml` 加入以下配置 :
        ```
        <configuration>
                <property>
                        <name>yarn.resourcemanager.hostname</name>
                        <value>Master</value>
                </property>
                <property>
                        <name>yarn.nodemanager.aux-services</name>
                        <value>mapreduce_shuffle</value>
                </property>
        </configuration>
        ```
* 將 `Master` 上的 `/usr/local/hadoop` 資料夾複製到其他節點。
```
$ cd /usr/local
$ tar -zcf ~/hadoop.master.tar.gz ./hadoop
$ cd ~
$ scp ./hadoop.master.tar.gz Datanode1:/home/hadoop
$ scp ./hadoop.master.tar.gz Datanode2:/home/hadoop
$ scp ./hadoop.master.tar.gz Datanode3:/home/hadoop
```
* 在其他節點上執行以下命令來解壓縮並更改 owner : 
```
$ sudo tar -zxf ~/hadoop.master.tar.gz -C /usr/local
$ sudo chown -R hadoop /usr/local/hadoop
```
### Step 5
==Master 主機==
* 在環境變數 `~/.bashrc` 內加入其他變數，需要加在 `$JAVA_HOME` 後面 : 
```
export HADOOP_MAPRED_HOME=/usr/local/hadoop
export PATH=$PATH:/usr/local/hadoop/bin:/usr/local/hadoop/sbin:$JAVA_HOME/bin
```
* 下命令 `$ source ~/.bashrc` 來啟用變數。
* 初始化 NameNode ，只需要跑第一次，之後啟動都不用跑 : 
```
$ hdfs namenode -format
```
* 下命令來啟動 NameNode 與 DataNode : 
```
$ start-dfs.sh
Starting namenodes on [Master]
Starting datanodes
Starting secondary namenodes [Master] 
```
* 啟動 `yarn` 資源管理 :
```
$ start-yarn.sh
Starting resourcemanager
Starting nodemanagers
```
* 在各節點下命令 `$ jps` 應該會看到目前有啟用的 hadoop daemon 。
    * 在 `Master` 上應該看到 : 
     
        ```
        $ jps
        7379 NameNode
        7670 SecondaryNameNode
        7944 ResourceManager
        8266 Jps
        ```
    * 在 `Datanode1` 、 `Datanode2` 、 `Datanode3` 應該看到 : 
        ```
        $ jps
        6150 Jps
        5784 DataNode
        5960 NodeManager 
        ```
* 成功之後，應該可以透過 `Master` 的瀏覽器連線到 `http://localhost:9870` 看到 hadoop 的管理介面。
![](https://i.imgur.com/pUsMNhO.png)
* 若要連線到 YARN 的 ResourceManager 介面，可以連線到 `http://master:8088` 。
![](https://i.imgur.com/6J55nKz.png)

* 要停止 hadoop ，可以下 `$ stop-all.sh` 命令。
    
### Reference
[hadoop 3.1.1 安裝教程](https://ubock.com/article/64)
[hadoop 集群配置教程](http://dblab.xmu.edu.cn/blog/install-hadoop-cluster/?fbclid=IwAR2pyMQpcR9Hw_wPDR7-D0mcFHNe41km6c-a3CoKR0_MgJCtMk2AsjThP-s)
[hadoop cluster setup](https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/ClusterSetup.html)
[hdfs command](https://hadoop.apache.org/docs/r1.0.4/cn/hdfs_shell.html)
[hadoop shell command](https://hadoop.apache.org/docs/r1.0.4/cn/hdfs_shell.html)

---

## Hadoop MapReduce Application
這裡會使用最簡單的 `WordCount.java` 程式來執行 MapReduce 。
### Step 1
* 加入 `HADOOP_CLASSPATH` 環境變數至 `~/.bashrc` 並啟用。
```
export HADOOP_CLASSPATH=$JAVA_HOME/lib/tools.jar
```
* 產生官方的 `WordCount.java` 檔案 : 
```java=
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);
  }
}
```
* 編譯 `WordCount.java` : 
```
$ hadoop com.sun.tools.javac.Main WordCount.java 
```
* 雖然會報 `HADOOP_COM.SUN.TOOLS.JAVAC.MAIN_OPTS: bad substitution` 的錯誤，不過還是能產生 class 檔。
* 將 `WordCount*.class` 合成一個 jar 檔，名為 `wc.jar` : 
```
$ jar cf wc.jar WordCount*.class
```
### Step 2
* 在 HDFS 上建立 **輸入** 資料夾，不用建立 **輸出** 資料夾。
```
$ hadoop fs -mkdir -p /user/hadoop/input
```
* 產生兩個 file ， `testfile` 與 `testfile2` ，並將其放入 HDFS 的 `input` 。
```
$ hadoop fs -put testfile /user/hadoop/input/testfile
$ hadoop fs -put testfile2 /user/hadoop/input/testfile2
```
* 下 `$ hadoop fs -ls /user/hadoop/input
` 應該會看到兩個檔案。
```
Found 2 items
-rw-r--r--   3 hadoop supergroup         46 2019-05-08 01:24 /user/hadoop/input/testfile
-rw-r--r--   3 hadoop supergroup         28 2019-05-11 01:00 /user/hadoop/input/testfile2
```
* 執行 `WordCount` : 
```
$ hadoop jar wc.jar WordCount /user/hadoop/input /user/hadoop/output
```
* 最後會獲得一連串的 log 。
```
2019-05-11 01:05:02,272 INFO client.RMProxy: Connecting to ResourceManager at Master/10.1.1.13:8032
2019-05-11 01:05:02,766 WARN mapreduce.JobResourceUploader: Hadoop command-line option parsing not performed. Implement the Tool interface and execute your application with ToolRunner to remedy this.
 .....
	File Input Format Counters 
		Bytes Read=74
	File Output Format Counters 
		Bytes Written=38
```
* 可以透過下命令 `$hadoop fs -ls /user/hadoop/output` 來確認 MapReduce 結果是否成功，若有 `_SUCCESS` 則表示成功。
```
Found 2 items
-rw-r--r--   3 hadoop supergroup          0 2019-05-11 01:05 /user/hadoop/output/_SUCCESS
-rw-r--r--   3 hadoop supergroup         38 2019-05-11 01:05 /user/hadoop/output/part-r-00000
```
* 透過下命令 `$ hadoop fs -cat /user/hadoop/output/part-r-00000` 來查看結果。
```
BBye	2
Bye	2
Hadoop	2
Hello	4
World	3
```

### Reference
[MapReduce Tutorial](https://hadoop.apache.org/docs/r3.2.0/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html#Example:_WordCount_v1.0)
[IT hadoop 鐵人競賽](https://ithelp.ithome.com.tw/articles/10191116)
[MapReduce Hadoop Multiple Input](http://rightthewaygeek.blogspot.com/2014/11/java-big-data4-hadoop-multiple-input.html)

---

## Spark 安裝套件 (Working)
* [Apache Spark](https://spark.apache.org/downloads.html)

## 手動安裝 Spark
* 解壓縮檔案後將目錄一致 `/usr/local` 底下。
```
$ cd ~/Downloads
$ tar -xvf spark-2.4.3-bin-hadoop2.7.tgz -C /usr/local
$ sudo mv ./spark-2.4.3-bin-hadoop2.7 ./spark
```
* 修改 `~/.bashrc` 來加入 Spark 與 HADOOP_CONF_DIR 的環境變數，記得 `soruce ~/.bashrc` 套用變數。
```
export HADOOP_MAPRED_HOME=/usr/local/hadoop
export SPARK_HOME=/usr/local/spark
export HADOOP_CONF_DIR=$HADOOP_MAPRED_HOME/etc/hadoop
export PATH=$PATH:/usr/local/hadoop/bin:/usr/local/hadoop/sbin:$JAVA_HOME/bin:$SPARK_HOME/bin
```

* 安裝 python
* 安裝 Scala

### Reference
* https://spark.apache.org/docs/latest/running-on-yarn.html
* https://github.com/amueller/word_cloud
* https://github.com/fxsjy/jieba
* https://github.com/jwlin/ptt-web-crawler
* https://spark.apache.org/docs/2.0.0/mllib-statistics.html
* https://www.oreilly.com/library/view/learning-spark/9781449359034/ch04.html
* http://rightthewaygeek.blogspot.com/2014/11/java-big-data4-hadoop-multiple-input.html


