# 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 的管理介面。

* 若要連線到 YARN 的 ResourceManager 介面,可以連線到 `http://master:8088` 。

* 要停止 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