從零開始學習大數據平臺(Episode 1)

(零)前言

本文記錄了從零開始,使用Hadoop(HDFS)+Spark,
建立不同的集羣,用Java/Python/Scala語言編寫實例程序的過程。

在這裏插入圖片描述
Hadoop® :是一個分佈式大數據的計算框架,主要組成:

  • HDFS™(Hadoop Distributed File System):高吞吐分佈式文件系統。
  • MapReduce: 基於YARN的大數據並行計算引擎。
  • YARN: 任務調度和集羣資源管理框架。
    在這裏插入圖片描述
    Spark™ :是一個大數據處理分析引擎。它相比Hadoop(MapReduce)效率更高,特別是邏輯運算。
    在這裏插入圖片描述

(一)準備集羣虛擬機與操作系統

(1.1)準備Master虛擬機

【1】選擇CentOS/安裝或者複用已有設備
選擇虛擬機是最方便的,因爲資源有限並且平時需要Windows進行管理開發。當然如果已經有現成的Linux環境無論是RHEL還是CentOS或者ubuntu都可以拿來用。

  • 在我實際工作環境中,客戶提供的環境通常是從資源池中分配的虛擬機,並安裝RHEL系統。 所以我選擇CentOS作爲測試環境。
  • 由作爲Master的虛擬機通常是複用的(Eclipse,IDEA,PAServer,FTP/WEB服務器,網頁文件瀏覽等等…),所以一般我們安裝帶有圖形界面的,各種庫比較完整的系統。
  • 安裝CentOS時自帶的是gnome桌面,爲了方便和好看,我選擇了Cinnamon桌面。

【2】下載虛擬機ISO
下載完整的CentOS7 DVD,目前版本是1810。
當然是官網下載最好:https://www.centos.org/download/
在這裏插入圖片描述
以防萬一我們通過release notes裏面的信息來驗證下載的DVD文件。

sha256sum x86_64:
38d5d51d9d100fd73df031ffd6bd8b1297ce24660dc8c13a3b8b4534a4bd291c
CentOS-7-x86_64-Minimal-1810.iso
6d44331cc4f6c506c7bbe9feb8468fad6c51a88ca1393ca6b8b486ea04bec3c1
CentOS-7-x86_64-DVD-1810.iso

【3】準備VMware安裝虛擬機
安裝前先設置好網絡環境,估計好虛擬機的CPU、內存、磁盤空間資源。並去掉不需要的設備。
因爲我們需要訪問局域網中運行於其它計算機上的虛擬機,所以網絡選擇橋接模式。
VMware的虛擬網絡編輯器和虛擬機本身都要設置好橋接,以及橋接的網絡。

虛擬機的磁盤最好是預先分配好空間,避免慢慢擴展帶來的外層磁盤碎片。
在這裏插入圖片描述
在這裏插入圖片描述
【4】開始安裝虛擬機
咱不是運維,所以通過圖形界面,下一步,下一步就可以安裝好。
其中可以先指定好root的密碼(這裏可以設置短一些),順便新建一個用戶。
磁盤可以用自動分區(以後不夠了可以擴),設置好網絡IP等。

全部完成後大概是這樣的:
在這裏插入圖片描述

(1.2)準備Workers虛擬機

【1】最小安裝
工作站(Workers)虛擬機和安裝Master虛擬機的步驟幾乎是一樣的,但是爲了節約空間(作爲HDFS的Datanode)應該進行最小安裝,沒有必要裝圖形桌面等,可以下載CentOS Minimal ISO 安裝。

先安裝一臺,全部設置好以後,再拷貝成多臺Worker虛擬機。

全部完成後大概是這樣的:
在這裏插入圖片描述
【2】要啥啥沒有
因爲是最小安裝,很多常用命令都沒有。所以很多東西需要手動裝一下,要不然真的是很不方便。
在這裏插入圖片描述

(1.3)設置虛擬機環境

運行Hadoop和Spark至少需要用到SSHJava環境

【1】配置主機網絡(Master和Slave都要配好)
如果安裝過程中沒有配置好網絡,則需要先配好網絡,否則剩下的步驟都無法進行。
根據實際情況配置好網絡:IP,掩碼,網關,DNS。
文件名有可能不是這個,注意看看。ONBOOT=yes表明開機網絡自動啓動。

$ sudo vim /etc/sysconfig/network-scripts/ifcfg-ens32 ————根據實際情況配置下述內容

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=no
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens32
DEVICE=ens32
ONBOOT=yes
IPADDR=192.168.2.222
PREFIX=24
GATEWAY=192.168.2.254
DNS1=192.168.2.254

然後重啓網絡服務吧

$ systemctl restart network

【2】關閉防火牆 (Master和Slave都關閉最方便)

$ systemctl disable firewalld
$ systemctl stop firewalld

【3】安裝ssh服務 (Master和Slave都要安裝)
網通了就可以直接yum進行安裝了,如果安裝中發現已經有了就可以跳過。

$ sudo yum install openssh

可以通過指令確定ssh服務的狀態,和控制它。
看到 Active: active (running) 就OK了。

$ systemctl status sshd ————查看ssh服務的狀態
$ systemctl start sshd ————啓動ssh服務
$ systemctl stop sshd ————停止ssh服務
$ systemctl disable sshd ————開機禁用ssh服務
$ systemctl enable sshd ————開機啓用ssh服務

【4】配置ssh密鑰方式登錄(免密碼登錄)
因爲是Master訪問各個Worker,所以在Master機器上生成Master的密鑰。

$ ssh-keygen -t rsa ————私鑰和公鑰將生成到用戶目錄/.ssh中,如下:

Your identification has been saved in /home/Shion/.ssh/id_rsa.
Your public key has been saved in /home/Shion/.ssh/id_rsa.pub.

因爲機器有限Master其實也是個Worker,所以自己的公鑰也要放入本機的authorized_keys
然後傳給剛纔裝好的Worker機。

$ cat /home/Shion/.ssh/id_rsa.pub >> /home/Shion/.ssh/authorized_keys
$ scp /home/Shion/.ssh/authorized_keys 192.168.2.222:/home/Shion/.ssh/authorized_keys

需要注意.ssh目錄和文件都不要讓別的用戶和組訪問,否則不生效:

  • ssh目錄的權限必須是700
  • ssh/authorized_keys文件權限必須是600

試一下,如果沒有成功則從Master ssh 到 Worker的時候還需要輸入密碼(成功就不要密碼了)。

【5】安裝JDK(Master和Slave都要安裝)
如果用OpenJDK的話,直接:

$ sudo yum install java

---> 軟件包 java-1.8.0-openjdk.x86_64.1.1.8.0.201.b09-2.el7_6 將被 安裝
...

不過我還是選擇去Oracle官網下載Java8的JDK,RPM包或者壓縮包:
Linux x64 168.05 MB jdk-8u201-linux-x64.rpm
Linux x64 182.93 MB jdk-8u201-linux-x64.tar.gz

下載了RPM後:

$ su
# rpm -ivh jdk-8u201-linux-x64.rpm
# vim /etc/profile ————在文件最後加入

export JAVA_HOME=/usr/java/default
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

然後重啓或者:

# source /etc/profile

【6】隨自己喜好更改和設置(可以先略過)

安裝vim

$ sudo yum install vim

提示符顏色格式設置

$ vim ~/.bashrc ————在文件最後添加下述內容

PS1='\$\[\033[01;32m\]\u\[\033[01;33m\]@\[\033[01;32m\]\h \[\033[01;34m\]\W\[\033[00m\]> '
export PS1

升級一下軟件

$ sudo yum upgrade

去掉多餘的內核
傳送門: 刪除多餘的RHEL(CentOS7)虛擬機Linux內核(啓動菜單)

安裝cinnamon桌面
其實可以先只裝一臺Master,當其它所有該安裝和配置的都完成以後,將Master複製一份作爲第一個Worker。最後再在Master上安裝桌面(此處以cinnamon爲例,其它桌面類似)。

# yum install epel-release ————安裝擴展源
# yum install yum-axelget ————提高yum下載速度
# yum -y groupinstall “X Window system”
# yum -y install lightdm
# yum install cinnamon ————這一步只安裝了cinnamon桌面本身。
# yum install gnome-terminal ————自動啓動圖形界面了,如果只裝了cinnamon本身,至少裝個終端先,要不本機登錄啥都幹不了。

只安裝cinnamon桌面,工具什麼的都沒有,或者就groupinstall吧:

# yum groupinstall “cinnamon” ————空間佔用比較大,有用沒用的都裝上先。

手動啓動圖形界面:

$ systemctl isolate graphical.target

自動啓動圖形界面:

# systemctl set-default graphical.target

如果刪錯過東西可能啓動不了,需要:

# yum -y groupinstall “Server with GUI”

如果正常的進入了圖形界面。
但是這時有可能是亂碼,因爲沒有字體,得安裝:

# yum groupinstall “fonts”

檢查中文環境:
*更新之前的方法並不是CentOS7下可用的
首先查看當前的系統語言:

# echo $LANG

如果不是中文,再看看有沒有語言包

# locale -a|grep zh_CN

不是中文的話:

# localectl set-locale LANG=zh_CN.UTF-8

或者:

# vim /etc/locale.conf

LANG=zh_CN.UTF-8

最後source或者重啓機器。
不過我係統語言是中文的情況下,cinnamon登錄進去的界面卻是英文。
並且可視化的語言設置有問題無法設置。所以只好:

$ vim ~/.bashrc

export LANG=zh_CN.utf8

(二)安裝Hadoop和Spark

(2.1)準備好全部計算機

我配置了6臺機器:

  • shionlnx ——(Hadoop Master/Datanode + Spark Master/Worker )—— 192.168.168.14
  • shion1 ——(Hadoop Datanode + Spark Worker ) —— 192.168.168.13
  • ac1 ——(Spark Worker )注:空間不足不用於HDFS —— 192.168.168.114
  • ac2 ——(Hadoop Datanode + Spark Worker ) —— 192.168.168.113
  • ad1 ——(Hadoop Datanode + Spark Worker ) —— 192.168.168.12
  • ad2 ——(Hadoop Datanode + Spark Worker )—— 192.168.168.11

master 和 第一臺slave可以各自安裝,也可以從Master拷貝出各個slave。
到此時每臺機器都應該配置好了網絡IP地址,機器名(hostname)。

【1】更改hosts文件便於用名稱訪問主機(每一臺主機都需要修改同樣內容)

# vim /etc/hosts ——增加如下內容(請按實際情況):

192.168.168.14 shionlnx
192.168.168.114 ac1
192.168.168.113 ac2
192.168.168.13 shion1
192.168.168.12 ad1
192.168.168.11 ad2

【2】更改hostname文(每一臺主機都需要分別修改,XXX是具體的名字)

# hostnamectl set-hostname XXX

先在Master機器上配置好Hadoop和Spark,然後再拷貝到其它Worker上。

(2.2)安裝配置Hadoop

【1】下載安裝包
當然是官網下載最好:https://hadoop.apache.org/releases.html
可以直接下載binary(編譯好的)。
保險起見,最好也檢查一下下載文件的checksum。
在這裏插入圖片描述
【2】解壓到你想放置的目錄

$ tar -zxvf hadoop-3.1.2.tar.gz -C /home/Shion ————解壓
$ mv /home/Shion/hadoop-3.1.2 /home/Shion/hadoop ————改名

【3】配置環境
先配置HADOOP_HOME

$ sudo vim /etc/profile ————在文件最後加入

#hadoop
export HADOOP_HOME=/home/Shion/hadoop
export PATH=$PATH:${HADOOP_HOME}/sbin
export PATH=$PATH:${HADOOP_HOME}/bin
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib/native"
export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native

進入{HADOOP_HOME}目錄配置:hadoop-env.sh

$ vim etc/hadoop/hadoop-env.sh ————在文件最後加入(這是hadoop目錄下的etc啊)

export JAVA_HOME=/usr/java/default

進入{HADOOP_HOME}目錄配置:core-site.xml

$ vim etc/hadoop/core-site.xml

<configuration>
        <property>
            <name>fs.defaultFS</name> <!--NameNode 的URI-->
            <value>hdfs://shionlnx:9000</value>
        </property>
        <property>
            <name>hadoop.tmp.dir</name> <!--hadoop臨時文件的存放目錄-->
            <value>/home/Shion/hadoop/temp</value>
        </property>
</configuration>

進入{HADOOP_HOME}目錄配置:hdfs-site.xml

$ vim etc/hadoop/hdfs-site.xml

<configuration>
      <property> <!--目錄無需預先存在,會自動創建-->
        <name>dfs.namenode.name.dir</name>
        <value>/home/Shion/hadoop/dfs/name</value><!---->
      </property>
      <property>  <!--目錄無需預先存在,會自動創建-->
        <name>dfs.datanode.data.dir</name>
        <value>/home/Shion/hadoop/dfs/data</value>
       </property>
      <property>  <!--數據副本數量,不能大於集羣的機器數量,默認爲3-->
        <name>dfs.replication</name>
        <value>3</value>
      </property>
      <property>  <!--設置爲true,可以在瀏覽器中IP+port查看-->
        <name>dfs.webhdfs.enabled</name>
        <value>true</value>
      </property>
            <property>  <!--dfs文件權限關閉(便於不同用戶訪問)-->
                <name>dfs.permissions</name>
                <value>false</value>
            </property>
            <property>  <!--動態下線的主機,配置文件名-->
                <name>dfs.hosts.exclude</name>
                <value>/home/Shion/hadoop/etc/hadoop/dfs_exclude</value>
            </property>
            <property>  <!--給HBase預留的最大傳輸線程數-->
  				<name>dfs.datanode.max.transfer.threads</name>
  				<value>4096</value>
			</property>
</configuration>

進入hadoop目錄配置:workers(以前叫slaves)

$ vim etc/hadoop/workers

shionlnx	
shion1
ac2
ad1
ad2

【4】從master整體拷貝hadoop目錄到worker

scp -r /home/Shion/hadoop shion1:/home/Shion/hadoop
scp -r /home/Shion/hadoop ac1:/home/Shion/hadoop
scp -r /home/Shion/hadoop ac2:/home/Shion/hadoop
scp -r /home/Shion/hadoop ad1:/home/Shion/hadoop
scp -r /home/Shion/hadoop ad2:/home/Shion/hadoop

(2.3)安裝配置Spark

【1】下載安裝包
當然是官網下載最好:http://spark.apache.org/downloads.html
咱的hadoop是3.1了,當然需要下載spark也得prebuilt4 hadoop 2.7+的。
在這裏插入圖片描述
【2】解壓到你想放置的目錄

$ tar -zxvf spark-2.4.1-bin-hadoop2.7.tgz -C /home/Shion ————解壓
$ mv /home/Shion/spark-2.4.1-bin-hadoop2.7 /home/Shion/spark ————改名

【3】配置環境
先配置SPARK_HOME

$ sudo vim /etc/profile ————在文件最後加入

#Spark
export SPARK_HOME=/home/Shion/spark
export PATH=$PATH:${SPARK_HOME}/bin

進入{SPARK_HOME}目錄配置:spark-defaults.conf

$ cp conf/spark-defaults.conf.template conf/spark-defaults.conf
$ vim conf/spark-defaults.conf

# Example:
# spark.master                     spark://master:7077
# spark.eventLog.enabled           true
# spark.eventLog.dir               hdfs://namenode:8021/directory
# spark.serializer                 org.apache.spark.serializer.KryoSerializer
spark.driver.memory                2g	#默認1g,如果driver的內存夠則不用設置這項。
# spark.python.worker.memory       768m
spark.executor.memory              2g	#一般不用設置。
# spark.driver.memoryOverhead      768m
# spark.executor.extraJavaOptions  -XX:+PrintGCDetails -Dkey=value -Dnumbers="one two three"
spark.executor.extraJavaOptions    -XX:-UseGCOverheadLimit	#一般不用設置,Java垃圾回收的預警(自行了解)
# spark.executor.memoryOverhead    512m
# spark.network.timeout            240s
# spark.default.parallelism        33
# spark.sql.shuffle.partitions     800
spark.ui.showConsoleProgress       true		#控制檯顯示進度(task/stage),配合log4j顯示WARN以上信息用。

進入{SPARK_HOME}目錄配置:spark-env.sh

$ cp conf/spark-env.sh.template conf/spark-env.sh
$ vim conf/spark-env.sh

export JAVA_HOME=/usr/java/default
export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native
export SPARK_MASTER_HOST=shionlnx
export SPARK_WORKER_CORES=1			#每個worker用到的cpu核心數
export SPARK_WORKER_MEMORY=2048m	#每個worker使用的內存
export SPARK_WORKER_INSTANCES=2		#每個主機的worker實例數(這個是本機生效)
export SPARK_DRIVER_MEMORY=2048m	#Driver能使用的內存

注意裏面的參數有些是Master上配置就生效,有些是主機自己的配置生效1

進入{SPARK_HOME}目錄配置:workers(這裏他們又叫slaves了。。暈。)

$ vim conf/slaves

shionlnx
shion1
ac1
ac2
ad1
ad2

進入{SPARK_HOME}目錄配置:log4j.properties

$ cp conf/log4j.properties.template conf/log4j.properties
$ vim conf/log4j.properties

# Set everything to be logged to the console
log4j.rootCategory=WARN, console	#主要就是這一句,否者控制檯輸出的信息多到啥都看不清了。
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n

# Set the default spark-shell log level to WARN. When running the spark-shell, the
# log level for this class is used to overwrite the root logger's log level, so that
# the user can have different defaults for the shell and regular Spark apps.
log4j.logger.org.apache.spark.repl.Main=WARN

# Settings to quiet third party logs that are too verbose
log4j.logger.org.spark_project.jetty=WARN
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO
log4j.logger.org.apache.parquet=ERROR

【4】從master整體拷貝spark目錄到worker

scp -r /home/Shion/spark shion1:/home/Shion/spark
scp -r /home/Shion/spark ac1:/home/Shion/spark
scp -r /home/Shion/spark ac2:/home/Shion/spark
scp -r /home/Shion/spark ad1:/home/Shion/spark
scp -r /home/Shion/spark ad2:/home/Shion/spark

(三)啓動HDFS和文件操作

(3.1)格式化HDFS

HDFS = Hadoop分佈式文件系統,是後續Spark或者Mapreduce的基礎。
進入{HADOOP_HOME}目錄執行:

$ bin/hdfs namenode -format

格式化之後就可以啓動HDFS了。
但是需要注意,如果重新格式化需要先將各個主機hdfs-site.xml中配置的數據和Name目錄刪掉。
對於我就是/home/Shion/hadoop/dfs/目錄。

否則會造成DataNode的clusterID不一致,導致出現問題。
clusterID是這樣格式的:CID-aaa66aaa-bb77-cc88-dd99-a1b2c3d4e5f6

(3.2)啓動HDFS

爲啥一定要用HDFS呢?
其實Spark程序是可以讀取主機本地目錄文件的,但是需要每臺worker都有相同的路徑和文件。。。
所以還是老老實實的用HDFS吧。

首先需要啓動HDFS,在Master服務器啓動hadoop,datanode節點會跟着啓動的。
進入{HADOOP_HOME}目錄執行:

$ sbin/start-dfs.sh

Starting namenodes on [shionlnx]
Starting datanodes
Starting secondary namenodes [shionlnx]

啓動完成後,可以輸入指令jps,看看NameNode,SecondaryNameNode,DataNode是否都存在。
當然worker機器只有DataNode。2

同理停止HDFS就是進入{HADOOP_HOME}目錄執行:

$ sbin/stop-dfs.sh

(3.3)使用HDFS

進入{HADOOP_HOME}目錄執行:

$ bin/hdfs dfs -help ——可以查看詳細的HDFS操作命令和參數,和本地文件操作其實差不多。

【1】列舉目錄/文件
比如列舉根目錄/,剛剛格式化完應該是空的,我這個例子中有幾個子目錄了。

$ bin/hdfs dfs -ls /

Found 4 items
drwxr-xr-x   - Shion supergroup          0 2019-04-10 09:19 /testdir0
drwxr-xr-x   - Shion supergroup          0 2019-04-08 09:34 /testdir1
drwxr-xr-x   - Shion supergroup          0 2019-04-08 10:30 /testdir2
drwxr-xr-x   - Shion supergroup          0 2019-04-08 13:45 /testdir3

【2】新建目錄

$ bin/hdfs dfs -mkdir /onemoredir

【3】上傳文件

$ bin/hdfs dfs -put .txt /onemoredir/
$ bin/hdfs dfs -ls /onemoredir/
.txt

-rw-r--r--   3 Shion supergroup     147145 2019-04-16 12:28 /onemoredir/LICENSE.txt
-rw-r--r--   3 Shion supergroup      21867 2019-04-16 12:28 /onemoredir/NOTICE.txt
-rw-r--r--   3 Shion supergroup       1366 2019-04-16 12:28 /onemoredir/README.txt

(3.4)瀏覽器查看HDFS情況

訪問缺省的HDFS的WEB UI,我這兒是:http://shionlnx:9870
可以查看整個HDFS的概述,數據節點的使用情況,以及文件查看以及上傳下載等。
在這裏插入圖片描述
在這裏插入圖片描述

(四)啓動Spark獨立模式集羣和運行程序

(4.1)本地模式執行

在啓動集羣以前,可以通過Scala Shell交互式執行代碼。
也可以Submit程序到Local(小數據測試程序挺有用的)

 spark-submit							#提交程序的指令
 --class Word2AuditTest 				#類名
 --master local 						#本地模式
 /somepath/word2audittest_2.11-1.0.jar 	#jar包
 /mnt/hgfs/ShareFolder/br1/cvm/			#程序的第一個參數,本地目錄
 200									#程序的第二個參數,類推

(4.2)啓動Spark獨立模式集羣

就是Spark standalone cluster,不使用YARN,Mesos。
進入{SPARK_HOME}目錄執行:

$ sbin/start-all.sh

starting org.apache.spark.deploy.master.Master, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.master.Master-1-shionlnx.out
shion1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-shion1.out
shionlnx: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-shionlnx.out
ac2: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-ac2.out
ac1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-ac1.out
ad1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-ad1.out
ad2: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-1-ad2.out
ac2: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-2-ac2.out
ac1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-2-ac1.out
shion1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-2-shion1.out
ad1: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-2-ad1.out
ad2: starting org.apache.spark.deploy.worker.Worker, logging to /home/Shion/spark/logs/spark-Shion-org.apache.spark.deploy.worker.Worker-2-ad2.out

啓動集羣以後,就可以將程序提交到集羣來執行了,那麼剛纔那個提交到本地執行的程序,就可以改寫成這樣:

 spark-submit							#提交程序的指令
 --class Word2AuditTest 				#類名
 --master spark://shionlnx:7077			#集羣模式
 /somepath/word2audittest_2.11-1.0.jar 	#jar包
 hdfs://shionlnx:9000/testdir2/			#程序的第一個參數,HDFS目錄
 200									#程序的第二個參數,類推

同理停止Spark集羣就是進入{SPARK_HOME}目錄執行:

$ sbin/stop-all.sh

(4.3)獨立集羣提交:部署模式

對於提交任務,spark-submit還有一個參數deploy-mode。

當不指定的時候,缺省是client模式。意思是driver運行在你提交任務的這臺主機上。
如果指定了cluster模式,那麼driver就會運行在某一臺worker主機上。

單個任務的時候感覺區別不大,但是多個任務同時執行時,client模式那臺提交任務的主機就要同時運行N個driver。這時候在cluster模式下,driver會自動分配到不同的worker主機,負載更加平衡呢。

官網是這麼說的:

--deploy-mode: Whether to deploy your driver on the worker nodes (cluster),
 or locally as an external client (client) (default: client) 

那麼剛纔那個提交的例子,就又可以改寫成這樣:

 spark-submit							#提交程序的指令
 --class Word2AuditTest 				#類名
 --master spark://shionlnx:7077			#獨立模式集羣
 --deploy-mode cluster					#集羣部署模式driver
 hdfs://shionlnx:9000/bin/Word2AuditTest-1.1.jar  	#jar包
 hdfs://shionlnx:9000/testdir2/			#程序的第一個參數,HDFS目錄
 200									#程序的第二個參數,類推
 hdfs://shionlnx:9000/output/			#程序的第三個參數,輸出目錄

(4.4)查看Spark和程序的狀態

【1】控制檯輸出

用上面的語句提交和執行程序時,控制檯輸出的信息比較少,因爲我們將log4j輸出做了設置,只輸出告警和錯誤信息。
當然也可以保持缺省設置INFO級別,那麼控制檯就會輸出全部信息,很多而且難以快速看到想要的信息。

log4j.rootCategory=WARN, console

我設置了這個參數:

spark.ui.showConsoleProgress       true

所以控制檯輸出差不多是這樣的(可以看到當前Stage和進度):
在這裏插入圖片描述
20分鐘後執行完了:
在這裏插入圖片描述
如果在cluster部署模式下的(–deploy-mode cluster),則因爲driver不在本地,命令行會很快返回。
所以這種模式下看不到任何日誌和錯誤輸出,需要進入WEB,才能查看Driver的輸出信息。

【2】用瀏覽器訪問Spark WEB UI

主頁面
訪問缺省的Spark的WEB UI,我這兒是:http://shionlnx:8080
可以看到一些基本情況包括Master的地址,Worker數量和列表,核心/內存等等。
下面有正在執行中的程序(應用)。
在這裏插入圖片描述
如果在cluster部署模式下的(–deploy-mode cluster),
則還可以看到正在運行的Driver,這時候的Driver就在一臺worker主機上了,並且佔用了一個Worker Instance 的Executor的資源。所以如果看運行的APP,就比client部署模式的少一個Executor。
在這裏插入圖片描述

應用(Aplication)頁面
點擊應用ID,可以看到應用使用的Executor,所屬的Worker,
以及輸出的日誌(在stderr裏):在這裏插入圖片描述

【2】訪問應用詳情WEB界面

應用詳情頁面:Jobs
從主頁點擊應用Name,或者從應用頁面點擊Application Detail UI,可以進入應用詳情網站。
在這個站頁面中,一個應用被分爲了若干個Job,每個Job又分爲幾個Stage,每個Stage會分成若干Task:
首先是Jobs頁面:
在這裏插入圖片描述
應用詳情頁面:Stages
然後是Stage頁面,點擊一個stage旁邊的detail,可以看到執行的函數階段):在這裏插入圖片描述
應用詳情頁面:Stage Detail
點擊一個Stage的描述文字,可以看到這個Stage的大概流程:
下面是這個Stage已經完成的Task的執行時間,Java資源回收時間,Shuffle讀取情況。
在這裏插入圖片描述
也可以展開Event Timeline(事件時間線)仔細查看:
在這裏插入圖片描述
應用詳情頁面:應用執行環境
這裏可以看到Java環境,Spark的各種屬性等。
最主要的是可以看到自己設置的一些變量到底設置成功了沒有(成功了就會在這裏面)。
比如剛纔提到的spark.ui.showConsoleProgress,以及我傳入的分區參數200:
在這裏插入圖片描述
執行器頁面
這裏可以看到Executor運行和歷史的情況。
在這裏插入圖片描述

(4.5)獨立集羣的備份高可用性(使用zookeeper)

默認情況下,獨立集羣對Worker的故障具有彈性(因爲Spark可以將工作從斷掉的Worker轉移到正常Worker上)。 但是,如果調度的Master出現故障,則不能創建新的應用程序。 爲了避免這種情況有兩個高可用性方案:

  1. 通過本地文件系統單點恢復。
  2. 使用zookeeper支持的備用Master。

第一種方式看官方文檔就OK了,而zookeeper:

ZooKeeper™ :致力於開發和維護開源服務器,實現高度可靠的分佈式協調。

在已經配置好zookeeper的情況下(我在shionlnx+shion1,兩臺機器上配置了zookeeper。當然考慮到主機故障,其實不應該把zookeeper和spark放一起,但是測試條件有限,將就一下)。

修改shionlnx和shion1主機的spark-env.sh文件。
註釋掉SPARK_MASTER_HOST或者SPARK_MASTER_IP,增加下面的項目。

$ vim $SPARK_HOME/conf/spark-env.sh

# export SPARK_MASTER_HOST=shionlnx
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=shionlnx:2181,shion1:2181 -Dspark.deploy.zookeeper.dir=/spark"

然後在shionlnx上正常啓動整個集羣:

$ $SPARK_HOME/sbin/start-all.sh

然後去shion1上啓動一個Master:

$ $SPARK_HOME/sbin/start-master.sh

這時可以看到shion1上的master是standby(準備中)狀態。
如果shionlnx上的master沒有問題,則它會一直standby。
如果shionlnx上的master崩潰了(爲了測試可以故意停掉),則它在幾秒鐘後就會變成alive狀態。
並且可以看到所有的work節點和正在運行的application和driver了。
在這裏插入圖片描述

(4.6)在YARN集羣上運行

Spark除了獨立集羣模式,還可以運行在YARN集羣上。
提交任務的命令需要將 master 從 spark 改爲 yarn。
那麼就不用啓動Spark集羣了,同時需要保證Yarn集羣正常運行中(如下)。

 spark-submit							#提交程序的指令
 --class Word2AuditTest 				#類名
 --master yarn							#YARN集羣
 --deploy-mode cluster					#集羣部署模式driver
 --driver-memory 2g 
 --executor-memory 2g 
 --executor-cores 2 
 hdfs://shionlnx:9000/bin/Word2AuditTest-1.1.jar  	#jar包
 hdfs://shionlnx:9000/testdir2/			#程序的第一個參數,HDFS目錄
 200									#程序的第二個參數,類推
 hdfs://shionlnx:9000/output/			#程序的第三個參數,輸出目錄

訪問Yarn的WEB UI:http://shionlnx:8088/
在Yarn中看到各個節點的資源情況。
在這裏插入圖片描述
在Yarn中看到應用運行列表。(這裏的ApplicationMaster,就是Spark Application Detail UI的入口)在這裏插入圖片描述
在Yarn中看到單個應用運行的情況。
在這裏插入圖片描述

(4.7)在Mesos集羣上運行

在這裏插入圖片描述
Mesos:將CPU/內存/存儲和其他計算資源從主機中抽象出來,構建成高效的容錯和彈性分佈式系統。

Spark還可以運行在Mesos集羣上:
提交任務的命令需要將 master 從 spark 改爲 Mesos。
那麼就不用啓動Spark集羣了,同時需要保證Mesos集羣正常運行中。

 spark-submit							#提交程序的指令
 --class Word2AuditTest 				#類名
 --master mesos://shionlnx:5050			#Mesos集羣
 hdfs://shionlnx:9000/bin/Word2AuditTest-1.1.jar  	#jar包
 hdfs://shionlnx:9000/testdir2/			#程序的第一個參數,HDFS目錄
 200									#程序的第二個參數,類推
 hdfs://shionlnx:9000/output/			#程序的第三個參數,輸出目錄

上面是client方式提交的,所以啓動的控制檯能看到運行日誌:
可以看到這個臨時搭建的集羣沒有任何授權和憑證。

*** AuditJava JX BOSSvsCRM 1.2 ***
I0424 12:28:28.273195 22215 sched.cpp:232] Version: 1.5.0
I0424 12:28:28.275389 22206 sched.cpp:336] New master detected at [email protected]:5050
I0424 12:28:28.275939 22206 sched.cpp:351] No credentials provided. Attempting to register without authentication
I0424 12:28:28.278688 22206 sched.cpp:751] Framework registered with 8558d550-d3b1-4586-8674-710bb2b22e99-0001
[Stage 7:============================>                         (105 + 11) / 200]

執行完後:

*** AuditJava JX BOSSvsCRM 1.2 ***
I0424 12:28:28.273195 22215 sched.cpp:232] Version: 1.5.0
I0424 12:28:28.275389 22206 sched.cpp:336] New master detected at [email protected]:5050
I0424 12:28:28.275939 22206 sched.cpp:351] No credentials provided. Attempting to register without authentication
I0424 12:28:28.278688 22206 sched.cpp:751] Framework registered with 8558d550-d3b1-4586-8674-710bb2b22e99-0001
001: 370042                                                                     
002: 114
存在並不一致: 399004
003: 189745
004: 216903
005: 54
I0424 12:49:54.716717 22157 sched.cpp:2009] Asked to stop the driver
I0424 12:49:54.717633 22208 sched.cpp:1191] Stopping framework 8558d550-d3b1-4586-8674-710bb2b22e99-0001
*** Task done ***

當然Mesos也是可以依靠zookeeper做多master備份的(*更新了一下,例中用zookeeper了)。
Mesos的WEBUI,對於Mesos + zookeeper的方式對用戶比較友好,無論你訪問哪一個master的地址,都可以看到同樣的頁面(本身還會自動刷新)。

此例中訪問 http://shionlnx:5050 或者 http://shion1:5050 都可以看到Leader爲shionlnx:5050的界面。
首頁能看到在Mesos中整體的資源情況,以及運行的框架(和分解的任務)。
在這裏插入圖片描述
正在執行的框架的資源情況。
在這裏插入圖片描述
連接上Master的代理(各種不同的用詞啊,Worker,Slave,Agent)
在這裏插入圖片描述
正在執行的一個應用的情況,包括資源,任務分解,
以及Spark的WEB UI(相當於Spark獨立模式裏面的 Application Detail UI)
在這裏插入圖片描述
一個應用各個任務的運行(沙盒)信息。
在這裏插入圖片描述
從一個代理的角度查看資源情況,運行的框架情況。
在這裏插入圖片描述
部署模式
運行在Mesos集羣下和運行在Spark獨立集羣模式下一樣,有兩種部署模式。
上面看到的都是Client部署模式,也就是Driver運行在本機的模式。
如果需要Driver運行在集羣,則也需要使用Cluster部署模式。
這時需要先啓動{SPARK_HOME}下的:sbin/start-mesos-dispatcher.sh,
並帶上參數–master mesos://shionlnx:5050

spark-submit 
--class com.ac.Word2AuditTest 
--master mesos://shionlnx:7077 						#通過dispatcher
--deploy-mode cluster 								#集羣部署模式
--supervise 										#如果失敗就重啓driver
--conf spark.master.rest.enabled=true 				#沒有這一行會報錯。。。
hdfs://shionlnx:9000/bin/Word2AuditTest-1.1.jar 	#程序
hdfs://shionlnx:9000 								#參數1
/testdir2/ 											#參數2
/output/ 											#參數3
200													#參數4

這時可以在Mesos中看到啓動的Framework裏面,有driver了:
*hint:不好意思有這麼多失敗的嘗試,具體問題可以看最後一章
在這裏插入圖片描述

並且可以看到Spark Drivers for Mesos Cluser(運行在Mesos上的Spark Drivers列表)
在這裏插入圖片描述

以及每個Driver的詳細情況。
在這裏插入圖片描述

(五)三種語言編寫示例

(5.1)Python與執行環境

【1】編譯環境

Python程序不用編譯,需要使用Spark的API:

from pyspark.sql import SparkSession
...
spark = SparkSession.builder.appName("應用的名字").getOrCreate()
...

【2】用Spark-submit提交

提交的方式也稍微不一樣,不需要指定類名,直接提交文件就OK。
官網的例子:

# Run a Python application on a Spark standalone cluster
./bin/spark-submit \
  --master spark://207.184.161.138:7077 \
  examples/src/main/python/pi.py \
  1000

【3】單獨執行程序

單獨執行只需要 ./appname.py 就可以了。
前提是Python的環境有PySpark。
當然也可能pip都沒有,那麼就yum install咯。

# pip install pyspark

然後程序中得指明spark的master(測試才寫死,正常可以用參數或者配置文件的形式啊。)

spark = SparkSession.builder
	.master("spark://shionlnx:7077")
	.appName("AuditPython_JX_BOSSvsCRM_5.6a")
	.getOrCreate()

【4】更多內容請自行了解
關於Python本身,或者pip,setup.py就請自行了解吧,spark相關內容可以多看看spark官網。

(5.2)Scala與執行環境

【1】編譯環境

Scala程序最終編譯成個jar包,需要sbt,官網:https://www.scala-sbt.org/
sbt = simple build tool ,是Scala, Java的編譯工具。

# yum install sbt

比較煩的是sbt貌似服務器被天朝的網絡限制了。。。
所以yum install sbt後,第一次執行sbt指令,會一直停在那沒反應。
我試過網上的教程,配置~/.sbt/repositories,設爲國內阿里源。但是第一次sbt中途會報錯???

[repositories]
local
ali: https://maven.aliyun.com/repository/public/

於是我從sbt官網下載了安裝包sbt-1.2.8.tgz,
發現比yum安裝的多一個lib/local-preloaded。。。
最後通過官網的安裝包+阿里源(repository)總算是成功安裝了。

【2】項目代碼/結構/打包

官網寫得很清楚,首先是項目目錄結構:

# 你的項目目錄結構應類似這樣
$ find .
.
./build.sbt
./src
./src/main
./src/main/scala
./src/main/scala/SimpleApp.scala

然後是用sbt進行打包:

# 在你的項目目錄中執行,打包成jar
$ sbt package
...
[info] Packaging {..}/{..}/target/scala-2.12/simple-project_2.12-1.0.jar

然後向spark提交程序:

# 用spark-submit運行你的程序
$ YOUR_SPARK_HOME/bin/spark-submit \
  --class "SimpleApp" \
  --master local[4] \
  target/scala-2.12/simple-project_2.12-1.0.jar

其中build.sbt內容類似這樣:

name := "Simple Project"
version := "1.0"
scalaVersion := "2.12.8"
libraryDependencies += "org.apache.spark" %% "spark-sql" % "2.4.1"

scala文件中必要的代碼大概如下(爲啥CSDN不高亮scala):

import org.apache.spark.sql.SparkSession
import org.apache.spark.rdd.PairRDDFunctions 
...
	val spark = SparkSession.builder.appName("Simple Application").getOrCreate()
    val logData = spark.read.textFile(logFile).cache()

(5.3)Java與執行環境

【1】編譯環境

Spark官網寫的用maven

# yum install maven

【2】項目代碼/結構/打包

官網寫得很清楚,首先是項目目錄結構:

# 你的項目目錄結構應類似這樣
$ find .
./pom.xml
./src
./src/main
./src/main/java
./src/main/java/SimpleApp.java

然後是用maven進行打包:

# 在你的項目目錄中執行,打包成jar
$ mvn package
...
[INFO] Building jar: {..}/{..}/target/simple-project-1.0.jar

然後向spark提交程序:

# 用spark-submit運行你的程序
$ YYOUR_SPARK_HOME/bin/spark-submit \
  --class "SimpleApp" \
  --master local[4] \
  target/simple-project-1.0.jar
...

其中pom.xml內容類似這樣:

<project>
  <groupId>edu.berkeley</groupId>
  <artifactId>simple-project</artifactId>
  <modelVersion>4.0.0</modelVersion>
  <name>Simple Project</name>
  <packaging>jar</packaging>
  <version>1.0</version>
  <dependencies>
    <dependency> <!-- Spark dependency -->
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.12</artifactId>
      <version>2.4.1</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

Java文件中必要的代碼大概如下:

import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.Dataset;
...
	SparkSession spark = SparkSession.builder().appName("Simple Application").getOrCreate();
    Dataset<String> logData = spark.read().textFile(logFile).cache();

(5.4)測試項目1:GZ_GPRS

GZ_GPRS數據稽覈,
BOSS文件格式:ISDN vs HSS文件格式:ISDN|IMSI
雙方共5000w記錄,進行存在性稽覈。
由於數據量比較小,Win單機32位程序都可以一次性處理,所以僅參考吧。
Spark稽覈項結果和一直在用的Windows業務規則輸出是一致的。

程序類型 處理時間 單位
Windows 單機單進程 2 分鐘
Java@Spark 2 分鐘
Scala@Spark 2.7 分鐘
Python@Spark 11 分鐘

在這裏插入圖片描述
在這裏插入圖片描述

(5.5)測試項目2:JX_BOSS/CRM

JX_BOSS/CRM數據稽覈,
雙方文件格式都是custID|custName|regNbr|regionCode
雙方共27000w記錄,
進行存在性稽覈,三個子項的差異性稽覈。
字段稍長,Win單機64位程序,需要自動拆分3次完成,內存使用峯值12GB多點。
Spark稽覈項結果和一直在用的Windows業務規則輸出也是一致的。

程序類型 處理時間 單位
Windows 單機單進程 47 分鐘
Java@Spark 20 分鐘
Scala@Spark 20 分鐘
Python@Spark 84 分鐘

【1】測試項目2:Python源代碼
可以命令行直接運行:

#!/usr/bin/python
#coding:utf-8
#

from __future__ import print_function

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

from operator import add
from pyspark.sql import SparkSession

if __name__ == "__main__":

    print("*** AuditPython JX BOSSvsCRM 5.6 ***")

    if len(sys.argv) < 2:
        print("Usage: AuditPython_JX_BOSSvsCRM <HDFS.Path.With/>", file=sys.stderr)
        sys.exit(-1)
        
    sparkB = SparkSession.builder.master("spark://shionlnx:7077").appName("AuditPython_JX_BOSSvsCRM_5.6b")
    if len(sys.argv) >= 3:
        sparkB.config("spark.default.parallelism",sys.argv[2])
    
    spark=sparkB.getOrCreate()

    dfboss = spark.read.csv(sys.argv[1]+'zk.cm_customer*.txt',sep='|', encoding='GBK')
    rddboss = dfboss.rdd.map(lambda r: (r[0],(r[1],r[2],r[3]))).reduceByKey(lambda x, y: x)	

    dfhss = spark.read.csv(sys.argv[1]+'zg.crm_customer*.txt',sep='|', encoding='GBK')
    rddhss = dfhss.rdd.map(lambda r: (r[0],(r[1],r[2],r[3]))).reduceByKey(lambda x, y: x)    	 	

    out_b_s = rddboss.fullOuterJoin(rddhss)
    
    g001=0
    f001 = open('/mnt/hgfs/ShareFolder/C_CUSTOMER_001_P.TXT', "w") 
    output001 = out_b_s.filter(lambda k: k[1][1]==None).collect()
    for (k,(v1,v2)) in output001:
        f001.write("%s|%s|%s|%s\n" % (k,v1[0],v1[1],v1[2]))
        g001+=1
    f001.close()           

    g002=0
    f002 = open('/mnt/hgfs/ShareFolder/C_CUSTOMER_002_P.TXT', "w") 
    output002 = out_b_s.filter(lambda k: k[1][0]==None).collect()
    for (k,(v1,v2)) in output002:
        f002.write("%s|%s|%s|%s\n" % (k,v2[0],v2[1],v2[2]))
        g002=g002+1
    f002.close()    

    g0xx=0
    g003=0
    g004=0
    g005=0
    f003 = open('/mnt/hgfs/ShareFolder/C_CUSTOMER_003_P.TXT', "w") 
    f004 = open('/mnt/hgfs/ShareFolder/C_CUSTOMER_004_P.TXT', "w") 
    f005 = open('/mnt/hgfs/ShareFolder/C_CUSTOMER_005_P.TXT', "w") 
    output003 = out_b_s.filter(lambda k: k[1][0]!=None and k[1][1]!=None and k[1][0]!=k[1][1]).collect()
    for (k,(v1,v2)) in output003:
        if v1[0]!=v2[0]:
          f003.write("%s|%s|%s\n" % (k,v1[0],v2[0]))
          g003=g003+1
        if v1[1]!=v2[1]:
          f004.write("%s|%s|%s\n" % (k,v1[1],v2[1]))
          g004=g004+1
        if v1[2]!=v2[2]:
          f005.write("%s|%s|%s\n" % (k,v1[0],v2[0]))
          g005=g005+1
        g0xx=g0xx+1
    f003.close()    
    f004.close()    
    f005.close()    

    print("001: %i" % g001)
    print("002: %i" % g002)
    print("存在並不一致: %i" % g0xx)
    print("003: %i" % g003)
    print("004: %i" % g004)
    print("005: %i" % g005)

    vlog = open('/mnt/hgfs/ShareFolder/CvB_SparkPythonLog.log', "w") 
    vlog.write("001: %i\n" % g001)
    vlog.write("002: %i\n" % g002)
    vlog.write("存在並不一致: %i\n" % g0xx)
    vlog.write("003: %i\n" % g003)
    vlog.write("004: %i\n" % g004)
    vlog.write("005: %i\n" % g005)
    vlog.close()    

    spark.stop()

    print("*** Application jod done ***")

【2】測試項目2:Scala源代碼
需要提交運行:

import org.apache.spark.sql.SparkSession
import org.apache.spark.rdd.PairRDDFunctions 
import java.io.PrintWriter
import java.io.File

object Word2AuditTest {
  def main(args: Array[String]) {

    println("*** AuditScala JX BOSSvsCRM 1.2 ***")

    if (args.length < 1) {
      System.err.println("Usage: AuditScala_JX_BOSSvsCRM <HDFS.Path.With/>")
      System.exit(1)
    }
    val aPath = args(0)
    
    val sparkB = SparkSession
    	.builder
    	.appName("AuditScala_JX_BOSSvsCRM_1.2")
    	
    if (args.length >= 2) {
    	sparkB.config("spark.default.parallelism",args(1))		  	
  	}
    	
    val spark =sparkB	
    	.getOrCreate()

    val dfboss = spark.read
    	.option("sep","|")
    	.option("encoding","GBK")
    	.csv(aPath+"zk.cm_customer*.txt")    
    val rddboss = dfboss.rdd.map(row => (row(0),(row(1),row(2),row(3)))).reduceByKey((x,y)=>(x))    
    
    val dfhss = spark.read
    	.option("sep","|")
    	.option("encoding","GBK")
    	.csv(aPath+"zg.crm_customer*.txt")
    val rddhss = dfhss.rdd.map(row => (row(0),(row(1),row(2),row(3)))).reduceByKey((x,y)=>(x)) 
    
    val out_b_s = rddboss.fullOuterJoin(rddhss)

    var g001 = 0
    val f001 = new PrintWriter("/mnt/hgfs/ShareFolder/C_CUSTOMER_001_S.TXT")     
    val output1 = out_b_s
    	.filter(_._2._2==None)
    	.collect()    
    for ((word,(value1,value2)) <- output1) {
    	val (v10,v11,v12) = value1.get;
      f001.println(s"$word|$v10|$v11|$v12")
      g001=g001+1
    }
    f001.close()            

    var g002 = 0
    val f002 = new PrintWriter("/mnt/hgfs/ShareFolder/C_CUSTOMER_002_S.TXT")     
    val output2 = out_b_s
    	.filter(_._2._1==None)
    	.collect()    
    for ((word,(value1,value2)) <- output2) {
    	val (v10,v11,v12) = value2.get;
      f002.println(s"$word|$v10|$v11|$v12")
      g002=g002+1
    }
    f002.close()
    
    var g0xx=0

    var g003 = 0
    var g004 = 0
    var g005 = 0
    val f003 = new PrintWriter("/mnt/hgfs/ShareFolder/C_CUSTOMER_003_S.TXT")     
    val f004 = new PrintWriter("/mnt/hgfs/ShareFolder/C_CUSTOMER_004_S.TXT")     
    val f005 = new PrintWriter("/mnt/hgfs/ShareFolder/C_CUSTOMER_005_S.TXT")     
    val output3 = out_b_s
    	.filter(v => (v._2._1!=None && v._2._2!=None && v._2._2!=v._2._1))
    	.collect() 
    	   
    for ((word,(value1,value2)) <- output3) {
    	val (v10,v11,v12) = value1.get;
    	val (v20,v21,v22) = value2.get;
      if (v10!=v20) {
	      f003.println(s"$word|$v10|$v20")
	      g003=g003+1
	    }   	
      if (v11!=v21) {
	      f004.println(s"$word|$v11|$v21")
	      g004=g004+1
	    }     	
      if (v12!=v22) {
	      f005.println(s"$word|$v12|$v22")
	      g005=g005+1      	
      }
      g0xx=g0xx+1
    }
    f003.close()
    f004.close()
    f005.close()
    
    println(s"001: $g001")
    println(s"002: $g002")
    println(s"存在並不一致: $g0xx")
    println(s"003: $g003")
    println(s"004: $g004")
    println(s"005: $g005")

    val flog = new PrintWriter("/mnt/hgfs/ShareFolder/CvB_SparkScalaLog.log") 
    flog.println(s"001: $g001")
    flog.println(s"002: $g002")
    flog.println(s"存在並不一致: $g0xx")
    flog.println(s"003: $g003")
    flog.println(s"004: $g004")
    flog.println(s"005: $g005")
    flog.close()
    
    spark.stop()
    
    println("*** Task done ***")

  }
}

【3】測試項目2:Java源代碼
需要提交運行:
*Hint1Java源代碼已經更新,已適應cluster部署模式提交。
*Hint2如果需要直接運行,請看最下面的問題列表。

package com.ac;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.Optional;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import scala.Tuple2;

import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.Arrays;
import java.util.List;

public final class Word2AuditTest {
    private static FileSystem getFileSystem(String ahdfs) throws Exception {
        return FileSystem.get(new URI(ahdfs),new  Configuration());
    }

    public static void main(String[] args) throws Exception {

        System.out.println("*** AuditJava JX BOSSvsCRM 1.2 ***");

        if (args.length < 3) {
            System.err.println("Usage: Word2AuditTest <HDFS> <Pathin/> <Pathout/> [ParaNum]");
            System.exit(1);
        }
        String aPath = args[0]+args[1];
        String aOutPath = args[0]+args[2];

        SparkSession.Builder sparkB = SparkSession
                .builder()
                .appName("AuditJava_JX_BOSSvsCRM_1.2");

        if (args.length >= 4) {
            sparkB.config("spark.default.parallelism", args[3]);
        }



        SparkSession spark = sparkB.getOrCreate();

        JavaRDD<Row> dfboss = spark.read()
                .option("sep", "|")
                .option("encoding", "GBK")
                .csv(aPath + "zk.cm_customer*.txt")
                .javaRDD();
        JavaPairRDD<String, String[]> rddboss = dfboss
                .mapToPair(s ->
                        new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(2), s.getString(3)})
                )
                .reduceByKey((i1, i2) -> i1);

        JavaRDD<Row> dfhss = spark.read()
                .option("sep", "|")
                .option("encoding", "GBK")
                .csv(aPath + "zg.crm_customer*.txt")
                .javaRDD();
        JavaPairRDD<String, String[]> rddhss = dfhss
                .mapToPair(s ->
                        new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(2), s.getString(3)})
                )
                .reduceByKey((i1, i2) -> i1);


        JavaPairRDD<String, Tuple2<Optional<String[]>, Optional<String[]>>> out_b_s = rddboss.fullOuterJoin(rddhss);

        FileSystem fs = getFileSystem(args[0]);

        int g001 = 0;
        FSDataOutputStream oFf001 = fs.create(new Path(String.format("%sC_CUSTOMER_001_J.TXT", aOutPath)));
        OutputStreamWriter oF001 = new OutputStreamWriter(oFf001);
        List<Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>>> output1 = out_b_s.filter(s -> !s._2()._2().isPresent()).collect();
        for (Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>> tuple : output1) {
            if (tuple._1().length() != 0) {
                oF001.write(String.format("%s|%s|%s|%s\n", tuple._1(), tuple._2()._1().get()[0], tuple._2()._1().get()[1], tuple._2()._1().get()[2]));
                g001++;
            }
        }
        oF001.close();

        int g002 = 0;
        FSDataOutputStream oFf002 = fs.create(new Path(String.format("%sC_CUSTOMER_002_J.TXT", aOutPath)));
        OutputStreamWriter oF002 = new OutputStreamWriter(oFf002);
        List<Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>>> output2 = out_b_s.filter(s -> !s._2()._1().isPresent()).collect();
        for (Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>> tuple : output2) {
            if (tuple._1().length() != 0) {
                oF002.write(String.format("%s|%s|%s|%s\n", tuple._1(), tuple._2()._2().get()[0], tuple._2()._2().get()[1], tuple._2()._2().get()[2]));
                g002++;
            }
        }
        oF002.close();

        int g0xx = 0;

        int g003 = 0;
        int g004 = 0;
        int g005 = 0;
        FSDataOutputStream oFf003 = fs.create(new Path(String.format("%sC_CUSTOMER_003_J.TXT", aOutPath)));
        OutputStreamWriter oF003 = new OutputStreamWriter(oFf003);
        FSDataOutputStream oFf004 = fs.create(new Path(String.format("%sC_CUSTOMER_004_J.TXT", aOutPath)));
        OutputStreamWriter oF004 = new OutputStreamWriter(oFf004);
        FSDataOutputStream oFf005 = fs.create(new Path(String.format("%sC_CUSTOMER_005_J.TXT", aOutPath)));
        OutputStreamWriter oF005 = new OutputStreamWriter(oFf005);
        List<Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>>> output3 = out_b_s.filter(s
                -> s._2()._1().isPresent()
                && s._2()._2().isPresent()
                && !Arrays.equals(s._2()._1().get(), s._2()._2().get())
        ).collect();
        for (Tuple2<String, Tuple2<Optional<String[]>, Optional<String[]>>> tuple : output3) {
            if (!StringUtils.equals(tuple._2()._1().get()[0], tuple._2()._2().get()[0])) {
                oF003.write(String.format("%s|%s|%s\n", tuple._1(), tuple._2()._1().get()[0], tuple._2()._2().get()[0]));
                g003++;
            }
            if (!StringUtils.equals(tuple._2()._1().get()[1], tuple._2()._2().get()[1])) {
                oF004.write(String.format("%s|%s|%s\n", tuple._1(), tuple._2()._1().get()[1], tuple._2()._2().get()[1]));
                g004++;
            }
            if (!StringUtils.equals(tuple._2()._1().get()[2], tuple._2()._2().get()[2])) {
                oF005.write(String.format("%s|%s|%s\n", tuple._1(), tuple._2()._1().get()[2], tuple._2()._2().get()[2]));
                g005++;
            }
            g0xx++;
        }
        oF003.close();
        oF004.close();
        oF005.close();

        System.out.printf("001: %d\n", g001);
        System.out.printf("002: %d\n", g002);
        System.out.printf("存在並不一致: %d\n", g0xx);
        System.out.printf("003: %d\n", g003);
        System.out.printf("004: %d\n", g004);
        System.out.printf("005: %d\n", g005);

        FSDataOutputStream fflog = fs.create(new Path(String.format("%sCvB_SparkJavaLog.log", aOutPath)));
        OutputStreamWriter flog = new OutputStreamWriter(fflog);
        flog.write(String.format("001: %d\n", g001));
        flog.write(String.format("002: %d\n", g002));
        flog.write(String.format("存在並不一致: %d\n", g0xx));
        flog.write(String.format("003: %d\n", g003));
        flog.write(String.format("004: %d\n", g004));
        flog.write(String.format("005: %d\n", g005));
        flog.close();
        spark.stop();

        System.out.println("*** Task done ***");
    }
}

(六)出現問題和解決的記錄

(6.1)分區數與executor內存不足

關鍵參數:spark.default.parallelism

官網文檔說明:
Default number of partitions in RDDs returned by transformations like join, reduceByKey, and parallelize when not set by user.
缺省取值說明:
For distributed shuffle operations like reduceByKey and join, the largest number of partitions in a parent RDD. For operations like parallelize with no parent RDDs, it depends on the cluster manager:

  • Local mode: number of cores on the local machine
  • Mesos fine grained mode: 8
  • Others: total number of cores on all executor nodes or 2, whichever is larger

通過無數報錯和測試,發現如果數據很大時,分區數會遠遠大於executor的核心數。具體的數值個人感覺應該是Spark通過輸入文件的大小估算的。

輸入文件和實際加載到內存中的數據其實並無絕對關聯,所以如果估算得不夠,那麼就可能造成下面的各種內存不足或者通信失敗的錯誤。3

許多文章提到的另一個參數 spark.sql.shuffle.partitions,在官方最新文檔2.4.1裏面沒有找到,不知道是不是取消了。畢竟Spark發展的方向時更加自動的處理。

【1】java.lang.OutOfMemoryError:GC overhead limit exceeded
通過統計GC時間來預測是否要OOM了,提前拋出異常,防止OOM發生。
Sun 官方對此的定義是:並行/併發回收器在GC回收時間過長時會拋出OutOfMemroyError。過長的定義是,超過98%的時間用來做GC並且回收了不到2%的堆內存。用來避免內存過小造成應用不能正常工作

簡單說就是一個Out of memory的預警異常,我們可以通過在spark-defaults.conf中:

spark.executor.extraJavaOptions    -XX:-UseGCOverheadLimit

把它關掉。當然關掉最可能的後果就是。。。Out of memory。

先看看程序有沒有什麼問題,排除代碼的問題後,就需要考慮是不是分區不夠,或者Executor內存分配得太小了。當然Driver內存不足也會報類似的錯誤,參考前後文可以看出到底是誰在報。

可以適當的增加二者內存的配置。但是注意不要超過物理內存了(我的Master兼做worker就是不小心設錯,逼近了物理內存的極限,所以有一陣跑比較大的數據時總是報此類內存不足的錯誤)

【2】java.lang.OutOfMemoryError: Java heap space
內存不足的另一個表現,可能是Executor或者Driver內存不足。

【3】Issue communicating with driver in heartbeater…: Exception thrown in awaitResult:
由於內存問題造成各種其它問題之一Executor到Driver的心跳信號超時。。。

【4】內存不足時Master控制檯未輸出報錯信息,但結果不正確
這個嘛……真的有點過分了。
不過當分區數設置夠了以後,結果每次都正確了。

(6.2)數據返回driver內存不足

類似下面的錯誤:

Uncaught exception in thread task-result-getter-0
java.lang.OutOfMemoryError: GC overhead limit exceeded

避免它貌似除了增加Driver的內存,就只有減小collect返回的數量了。
那麼如果我的確需要返回一個特別大的結果集,怎麼辦呢???

(6.3)找不到文件的各種原因

開始運行Spark示例程序的時候,我們通常用Spark本地模式,文件路徑也是本地路徑。
等需要提交到Spark獨立集羣時就會因爲workers上沒有路徑而報錯。
解決辦法:

  1. 用HDFS
  2. 保證每一臺主機擁有同樣的目錄/文件結構(是不是很傻)

當我們想用cluster部署方式提交到Spark集羣時,又會因爲driver所在的worker主機找不到程序jar文件。。。
解決辦法同樣如上。

同理完全集羣的方式下,日誌文件等都需要放入HDFS,也就是說不要出現任何本機目錄了。

(6.4)不提交(Spark-Submit)直接執行程序

考慮到業務系統調度引擎等因素,希望程序能直接運行,而不是隻能通過spark-submit。
*提示如果直接運行,則無法使用集羣部署模式(傳配置參數也不行),也就是說程序本體(drivier)只能執行在運行命令行的主機上,道理也非常明顯,你都用命令行執行程序了,它還能上哪兒呢。

Python程序貌似只要設好Spark Master,就可以直接運行。

$ ./appname.py

而scala和java的jar包卻沒有那麼簡單。
提交執行正常的程序,用命令行執行卻報錯,我遇到的主要幾個錯誤如下:

$ java -jar appname.jar …params…

【1】找不到主類,找不到各種依賴的包…
首先主類mainClass需要指定。
而且似乎直接執行的環境不是spark的java環境,而是系統的java環境。
所以maven打包時候需要修改pom.xml,增加這麼一個plugin配置項塊。
如下這樣才能執行,同時打包出來的jar就不是幾kb,而是100多mb了。。。

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>你程序的主類的名字</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

【2】java.io.IOException: No FileSystem for scheme: hdfs …
上網查原因好像是依賴的包重名,被打包成jar時打少了。
解決辦法是下面兩句,而且需要在spark.read()前執行getFileSystem:

private static FileSystem getFileSystem(String ahdfs) throws Exception {
	Configuration configuration = new Configuration();
	configuration.set("fs.defaultFS", "hadoop.user"); 									//增加的語句
	configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem"); 	//增加的語句
	return FileSystem.get(new URI(ahdfs),configuration);
}

【3】java.lang.ClassCastException:cannot assign instance of java.lang.invoke.SerializedLambda to …
上網查原因好像是分發到的worker無法解析lambda表達式(爲啥spark-submit可以???)。
解決辦法是在SparkSession.Builder裏面加SparkConf的setJars,當然如果直接用的是SparkConf那麼就只有一句。

SparkConf scf = new SparkConf();
scf.setJars(new String[]{"hdfs://shionlnx:9000/bin/Word2AuditTest-1.1.jar"}); //代碼中指定自己jar的位置,不奇怪麼?
sparkB.config(scf);

(6.5)Spark on Mesos - Cluster Deploy

【1】assertion failed: Mesos cluster mode is only supported through the REST submission API
需要在spark-submit的時候增加參數:–conf spark.master.rest.enabled=true

【2】Failed to create HDFS client: Hadoop client is not available
以及類似的訪問HDFS時找不到Hadoop的錯誤。
需要在Mesos的每個Agent配置環境變量:hadoop_home=/home/Shion/hadoop
以我的版本爲例子則是:

# vim /etc/mesos/mesos-agent-env.sh

增加這麼一行:

export MESOS_hadoop_home=/home/Shion/hadoop

其它版本Mesos可能不一樣。


在這裏插入圖片描述
本文爲工作內容記錄,會不定期的修改,
不要着急,更多的內容,待繼續補充……


  1. 我的每臺worker主機都時兩個實例,而master只准備啓動1個worker實例,所以拷貝完成後需要再次修改master本機的spark-env.sh,重新設置export SPARK_WORKER_INSTANCES=1。 ↩︎

  2. 因爲只啓動了HDFS,而沒有啓動YARN等。 ↩︎

  3. 經過非常多次的測試,發現如果分區數設置合理,那麼Shuffle時內存就一定夠。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章