# 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