文章目錄
(零)前言
本文記錄了從零開始,使用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至少需要用到SSH,Java環境。
【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出現故障,則不能創建新的應用程序。 爲了避免這種情況有兩個高可用性方案:
- 通過本地文件系統單點恢復。
- 使用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源代碼
需要提交運行:
*Hint1:Java源代碼已經更新,已適應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上沒有路徑而報錯。
解決辦法:
- 用HDFS
- 保證每一臺主機擁有同樣的目錄/文件結構(是不是很傻)
當我們想用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可能不一樣。
本文爲工作內容記錄,會不定期的修改,
不要着急,更多的內容,待繼續補充……