spark源碼編譯和叢集部署以及idea中sbt開發環境內建
- 源碼下載下傳
- 源碼編譯
- maven 下載下傳
- scala 下載下傳
- 編譯參數
- 編譯
- 編譯分發的二進制包
- 單機啟動
- 叢集部署
- 開發環境內建
- 源碼編譯的3.2.0版本無法在window上直接用spark-shell啟動
- 總結
項目位址:https://gitee.com/jyq_18792721831/studyspark.git
源碼下載下傳
打開Apache Spark™ - Unified Engine for large-scale data analytics,下載下傳源碼

在下載下傳界面中選擇源碼下載下傳
随便選擇哪個位址
下載下傳源碼後上傳到hadoop01伺服器的
/spark
目錄下
使用
tar -zxvf spark-3.2.0.tgz
解壓到
/spark
目錄下
源碼編譯
傳回下載下傳界面,選擇文檔,選擇最新版或者你下載下傳的版本
或者你可以直接打開Overview - Spark 3.2.0 Documentation (apache.org)界面
選擇跳轉到打包建構的文檔
有兩種建構方式:maven和sbt(scala build tool)
我們選擇maven
maven 下載下傳
在編譯文檔中,要求我們盡可能使用新的maven
是以我們使用手動安裝的方式,安裝最新版
首先打開Maven – Welcome to Apache Maven,跳轉到下載下傳頁
我們選擇最新的二進制包
右鍵拷貝連結,直接在hadoop01上下載下傳
在根目錄下建立
/maven
目錄,然後執行
curl -O https://dlcdn.apache.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz
下載下傳
使用
tar -zxvf apache-maven-3.8.4-bin.tar.gz
解壓
删除壓縮包,并把maven裡面的内容移動出來,使得maven的home為
/maven
因為之前就在hadoop01上安裝過了java,并且配置了環境變量,是以可以直接配置maven的環境變量即可,高版本需要jdk1.7以上
配置環境變量
然後使用
source ~/.bash_profile
生效
使用
mvn --version
驗證版本
scala 下載下傳
到The Scala Programming Language (scala-lang.org)進入下載下傳界面
選擇scala2吧,scala3實在沒用過。
拉到最下面,下載下傳二進制包
相同的方式,拷貝連結,在hadoop01中下載下傳
然後配置環境變量
使用
source ~/.bash_profile
生效後驗證版本
編譯參數
根據文檔,首先設定maven的資訊
我們将
MAVEN_OPTS
放入
~/.bash_profile
中
使用
source ~/.bash_profile
使之生效。
接着往下看建構文檔,找到支援hive和jdbc的建構
這是maven指令,使用源碼包中自帶的maven,其中
-P
和
-D
是一些參數。
這些參數和pom.xml檔案中的profile和properties有關。
-D
對應properties
-P
對應profile
我們檢視pom.xml檔案
這隻是一部分,具體的可以自行檢視
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 我們使用的是java 11,這個需要進行替換 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<!-- 因為我們使用的是3.8.1的maven,是以這個也需要替換 -->
<maven.version>3.6.3</maven.version>
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
<sbt.project.name>spark</sbt.project.name>
<slf4j.version>1.7.30</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<!-- hadoop 的版本是2.9.2 -->
<hadoop.version>3.3.1</hadoop.version>
<protobuf.version>2.5.0</protobuf.version>
<yarn.version>${hadoop.version}</yarn.version>
<zookeeper.version>3.6.2</zookeeper.version>
<curator.version>2.13.0</curator.version>
<hive.group>org.apache.hive</hive.group>
<hive.classifier>core</hive.classifier>
<!-- Version used in Maven Hive dependency -->
<!-- hive的版本是2.3.9,這個可以不用修改 -->
<hive.version>2.3.9</hive.version>
<hive23.version>2.3.9</hive23.version>
<!-- Version used for internal directory structure -->
<hive.version.short>2.3</hive.version.short>
<!-- note that this should be compatible with Kafka brokers version 0.10 and up -->
<kafka.version>2.8.0</kafka.version>
<!-- After 10.15.1.3, the minimum required version is JDK9 -->
<derby.version>10.14.2.0</derby.version>
<parquet.version>1.12.1</parquet.version>
<orc.version>1.6.11</orc.version>
<jetty.version>9.4.43.v20210629</jetty.version>
<jakartaservlet.version>4.0.3</jakartaservlet.version>
<chill.version>0.10.0</chill.version>
<ivy.version>2.5.0</ivy.version>
<oro.version>2.0.8</oro.version>
<!--
If you changes codahale.metrics.version, you also need to change
the link to metrics.dropwizard.io in docs/monitoring.md.
-->
<codahale.metrics.version>4.2.0</codahale.metrics.version>
<avro.version>1.10.2</avro.version>
<aws.kinesis.client.version>1.12.0</aws.kinesis.client.version>
<!-- Should be consistent with Kinesis client dependency -->
<aws.java.sdk.version>1.11.655</aws.java.sdk.version>
<!-- the producer is used in tests -->
<aws.kinesis.producer.version>0.12.8</aws.kinesis.producer.version>
<!-- org.apache.httpcomponents/httpclient-->
<commons.httpclient.version>4.5.13</commons.httpclient.version>
</properties>
是以,我們的建構指令中
-D
要設定的如下
mvn -Djava.version=11 -Dmaven.version=3.8.1 -Dhadoop.version=2.9.2 -Dscala.version=2.13.8 -DskipTests clean package
接下來是
-P
參數
-P
參數對應的是pom.xml中的profile屬性
<profile>
<id>hadoop-2.7</id>
<properties>
<hadoop.version>2.7.4</hadoop.version>
<curator.version>2.7.1</curator.version>
<commons-io.version>2.4</commons-io.version>
<hadoop-client-api.artifact>hadoop-client</hadoop-client-api.artifact>
<hadoop-client-runtime.artifact>hadoop-yarn-api</hadoop-client-runtime.artifact>
<hadoop-client-minicluster.artifact>hadoop-client</hadoop-client-minicluster.artifact>
</properties>
</profile>
<profile>
<id>hive-2.3</id>
</profile>
<profile>
<id>yarn</id>
<modules>
<module>resource-managers/yarn</module>
<module>common/network-yarn</module>
</modules>
</profile>
<profile>
<id>hive-thriftserver</id>
<modules>
<module>sql/hive-thriftserver</module>
</modules>
</profile>
<profile>
<id>scala-2.13</id>
<properties>
<scala.version>2.13.5</scala.version>
<scala.binary.version>2.13</scala.binary.version>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<configuration>
<args>
<arg>-unchecked</arg>
<arg>-deprecation</arg>
<arg>-feature</arg>
<arg>-explaintypes</arg>
<arg>-target:jvm-1.8</arg>
<arg>-Wconf:cat=deprecation:wv,any:e</arg>
<arg>-Wconf:cat=scaladoc:wv</arg>
<arg>-Wconf:cat=lint-multiarg-infix:wv</arg>
<arg>-Wconf:cat=other-nullary-override:wv</arg>
<arg>-Wconf:cat=other-match-analysis&site=org.apache.spark.sql.catalyst.catalog.SessionCatalog.lookupFunction.catalogFunction:wv</arg>
<arg>-Wconf:cat=other-pure-statement&site=org.apache.spark.streaming.util.FileBasedWriteAheadLog.readAll.readFile:wv</arg>
<arg>-Wconf:cat=other-pure-statement&site=org.apache.spark.scheduler.OutputCommitCoordinatorSuite.<local OutputCommitCoordinatorSuite>.futureAction:wv</arg>
<arg>-Wconf:msg=^(?=.*?method|value|type|object|trait|inheritance)(?=.*?deprecated)(?=.*?since 2.13).+$:s</arg>
<arg>-Wconf:msg=^(?=.*?Widening conversion from)(?=.*?is deprecated because it loses precision).+$:s</arg>
<arg>-Wconf:msg=Auto-application to \`\(\)\` is deprecated:s</arg>
<arg>-Wconf:msg=method with a single empty parameter list overrides method without any parameter list:s</arg>
<arg>-Wconf:msg=method without a parameter list overrides a method with a single empty one:s</arg>
<arg>-Wconf:cat=deprecation&msg=procedure syntax is deprecated:e</arg>
</args>
<compilerPlugins combine.self="override">
</compilerPlugins>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
是以
-P
的參數大概就這麼多
mvn -Phadoop-2.7 -Phive-2.3 -Pyarn -Phive-thriftserver -Pscala-2.13 -Djava.version=11 -Dmaven.version=3.8.4 -Dhadoop.version=2.9.2 -Dscala.version=2.13.8 -Dscala.binary.version=2.13 -DskipTests clean package
編譯
如果使用的是scala-2.13版本,那麼需要切換scala版本
檢視
change-scala-version.sh
的内容,主要是環境變量的配置和pom.xml檔案中的版本号的修改。
在
pom.xml
中,scala相關的配置是properties,理論上我們使用
-D
參數就能修改,不過可能有其他的配置,不一定是pom.xml的,反正執行一次最好。
而且,需要注意的是,執行這個指令,必須在spark主目錄下,因為在
change-scala-version.sh
腳本中使用相對路徑執行其他腳本,是以如果你在其他位置執行,那麼會因為上下文的問題,導緻執行失敗。
實際上,
change-scala-version.sh
腳本還會使用spark主目錄下
build/mvn
程式。
我們使用上面拼接好的指令,在
SPARK_HOME
下執行
第一次執行,maven 會下載下傳相關的依賴,比較慢
哎,依賴是在是多,耐心等待吧
等待了一段時間,發現編譯錯誤了
很正常,一次成功才不正常。
我們檢視錯誤的堆棧,發現是因為找不到一個為
RangePartitioner
的類,檢視源碼,這是一個在
apache/spark/blob/master/core/src/main/scala/org/apache/spark/Partitioner.scala
中定義的類
問題在于scala-2.13中沒有打包生成class檔案,而scala-2.12中就會打包生成calss檔案
生成的class檔案存儲在
/spark/core/target/scala-2.12/classes/org/apache/spark
目錄下
不知道是不是scala和spark的相容性問題。(我自己試了好多次,2.12.15可以編譯成功,2.13.8就編譯失敗了。)
在使用scala-2.12編譯的時候,編譯到spark-sql的時候,卡主,懷疑是記憶體不足了
幸好是虛拟機,可以增加記憶體,我們嘗試将記憶體增加到4G
然後終止編譯,重新編譯,并使用
-X
啟動debug輸出
經過嘗試,調大記憶體在一定程度上能加快編譯速度。
第一次編譯可以采用一步一步執行,可以先編譯後打包,其實打包也包含編譯,我單獨執行編譯是為了驗證scala-2.12是否會生成相關的class檔案,打包成功如下
編譯成功就會生成二進制檔案,存儲于spark根目錄的bin目錄下
其實這隻是一部分,這些腳本調用的jar包也會生成。
我們在源碼的bin目錄下就可以開心的使用spark了
直接啟動spark-shell
這些版本資訊和我們在編譯的時候指定的一模一樣。
編譯分發的二進制包
編譯好隻是這個環境編譯好了,裡面既有源碼檔案,也有可執行檔案,我們不能在叢集環境的每個節點上都進行編譯一次,那麼太過費時,而且也不可行。
是以我們需要編譯可分發的二進制包,就像官網提供的spark一樣,沒有源碼,全部是執行檔案。
在官網中是建議我們使用工具編譯分發的二進制包
檢視這個腳本,發現還是maven指令
最後将編譯後在bin目錄中的内容進行打包而已
我們在spark的根目錄下執行
./dev/make-distribution.sh --name 2.12 --tgz --mvn mvn -Phadoop-2.7 -Phive-2.3 -Pyarn -Phive-thriftserver -Pscala-2.12 -Djava.version=11 -Dmaven.version=3.8.4 -Dhadoop.version=2.9.2 -Dscala.version=2.12.15 -Dscala.binary.version=2.12 -DskipTests clean package
打包完成後就會生成
psark-2.9.2-bin-2.12.tgz
壓縮包。
并不會列印maven的日志,等待就行了,可能需要和maven打包同樣長的時間。(2核,8G,大約40分鐘)
完成後會在spark根目錄下生成可分發二進制檔案包
實際上當我們使用maven指令完成打包後,隻是單獨的想打包分發二進制包,那麼是完全不需要重新執行maven打包的,畢竟maven打包太慢了。我們可以将spark根目錄下的
./dev/make-distribution.sh
檔案中的相關maven的指令跳過,注釋掉腳本中的這兩行即可。
然後把原來腳本中使用maven擷取版本号的方式自定義指定即可
這樣就不會重複執行maven指令了(maven指令太費時間了)
然後在maven打包完成的前提下,執行分發的二進制包的指令
./dev/make-distribution.sh --name 2.12 --tgz --mvn mvn -Phadoop-2.7 -Phive-2.3 -Pyarn -Phive-thriftserver -Pscala-2.12 -Djava.version=11 -Dmaven.version=3.8.4 -Dhadoop.version=2.9.2 -Dscala.version=2.12.15 -Dscala.binary.version=2.12 -DskipTests clean package
spark源碼編譯實際上就是maven編譯,如果你了解maven編譯,那麼在spark源碼編譯中遇到的問題,你都能很好的解決。
而二進制分發包的打包腳本,就是對maven編譯的使用,然後在把編譯後的檔案重新組合。
單機啟動
我們将編譯好的二進制包,從hadoop01分發到hadoop02上,使用
scp spark-3.2.0-bin-2.12.tgz hadoop02:/spark/
然後在hadoop02上解壓,
tar -zxvf spark-3.2.0-bin-2.12.tgz
我們選擇在hadoop02上驗證二進制分發包是否可用。
然後将
SPARK_HOME
設定到環境變量,并使之生效
然後你可以在任意位置使用
spark-shell
啟動
相關的文檔在這裡Quick Start - Spark 3.2.0 Documentation (apache.org)
啟動後如下
可見啟動spark并不需要scala環境,應該把scala的相關的包內建進去了
我們嘗試一下quick-started中的例子
然後打開
http://hadoop02:4040
檢視執行曆史
這種啟動方式,在啟動中會列印如下資料
這其實是spark的standalone的啟動方式
相關的文檔在這Overview - Spark 3.2.0 Documentation (apache.org)
關于standalone的模式的相關文檔
Spark Standalone Mode - Spark 3.2.0 Documentation (apache.org)
叢集部署
為什麼直接啟動
spark-shell
就是單機版的模式呢?
spark的配置和hadoop差不多,有個slaves檔案,裡面會指定從節點的域名
預設的配置在
/spark/conf
檔案裡面
但是你在這裡面是找不到slaves檔案的,不過有一個workers.template檔案
檢視這個檔案,預設是localhost
在sbin目錄下有一個
slaves.sh
檔案
slaves.sh
檔案跳轉到了
workers.sh
檔案中了
通過這個腳本基本上就能明白了,spark的從節點配置在
/sbin/spark-config.sh
和
/conf
中都可以配置
其中所有的配置都可以在
/conf/spark-env.sh
中配置
這些是一些standalone的配置
基本上都是通用的。
我們搭建一個spark的叢集,叢集資訊如下
節點 | 角色 | 其他 |
hadoop01 | 主節點 | 一個執行器 |
hadoop02 | 從節點 | 一個執行器 |
hadoop03 | 從節點 | 一個執行器 |
完整的
/conf/spark-env.sh
如下
# Options read when launching programs locally with
# ./bin/run-example or ./bin/spark-submit
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public dns name of the driver program
# hadoop 的配置資訊
HADOOP_CONF_DIR=/hadoop/etc/hadoop
# Options read by executors and drivers running inside the cluster
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public DNS name of the driver program
# - SPARK_LOCAL_DIRS, storage directories to use on this node for shuffle and RDD data
# - MESOS_NATIVE_JAVA_LIBRARY, to point to your libmesos.so if you use Mesos
# spark shuffle的資料目錄,
SPARK_LOCAL_DIRS=/spark/shuffle_data
# Options read in YARN client/cluster mode
# - SPARK_CONF_DIR, Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files
# - YARN_CONF_DIR, to point Spark towards YARN configuration files when you use YARN
# - SPARK_EXECUTOR_CORES, Number of cores for the executors (Default: 1).
# - SPARK_EXECUTOR_MEMORY, Memory per Executor (e.g. 1000M, 2G) (Default: 1G)
# - SPARK_DRIVER_MEMORY, Memory for Driver (e.g. 1000M, 2G) (Default: 1G)
# yarn的配置檔案在hadoop的配置檔案中
YARN_CONF_DIR=$HADOOP_CONF_DIR
# 每個節點啟動幾個執行器
SPARK_EXECUTOR_CORES=1
# 每個執行器可以使用多大記憶體
SPARK_EXECUTOR_MEMORY=1800M
# 每個driver可以使用多大記憶體
SPARK_DRIVER_MEMORY=1800M
# Options for the daemons used in the standalone deploy mode
# - SPARK_MASTER_HOST, to bind the master to a different IP address or hostname
# - SPARK_MASTER_PORT / SPARK_MASTER_WEBUI_PORT, to use non-default ports for the master
# - SPARK_MASTER_OPTS, to set config properties only for the master (e.g. "-Dx=y")
# - SPARK_WORKER_CORES, to set the number of cores to use on this machine
# - SPARK_WORKER_MEMORY, to set how much total memory workers have to give executors (e.g. 1000m, 2g)
# - SPARK_WORKER_PORT / SPARK_WORKER_WEBUI_PORT, to use non-default ports for the worker
# - SPARK_WORKER_DIR, to set the working directory of worker processes
# - SPARK_WORKER_OPTS, to set config properties only for the worker (e.g. "-Dx=y")
# - SPARK_DAEMON_MEMORY, to allocate to the master, worker and history server themselves (default: 1g).
# - SPARK_HISTORY_OPTS, to set config properties only for the history server (e.g. "-Dx=y")
# - SPARK_SHUFFLE_OPTS, to set config properties only for the external shuffle service (e.g. "-Dx=y")
# - SPARK_DAEMON_JAVA_OPTS, to set config properties for all daemons (e.g. "-Dx=y")
# - SPARK_DAEMON_CLASSPATH, to set the classpath for all daemons
# - SPARK_PUBLIC_DNS, to set the public dns name of the master or workers
# 主節點
SPARK_MASTER_HOST=hadoop01
# 端口,不要和worker的端口重複,通信端口
SPARK_MASTER_PORT=4040
# 界面端口
SPARK_MASTER_WEBUI_PORT=8089
# 每個從節點啟動的執行器
SPARK_WORKER_CORES=1
# 每個從節點可使用最大記憶體
SPARK_WORKER_MEMORY=1800M
# 從節點的通信端口
SPARK_WORKER_PORT=4040
# 界面端口
SPARK_WORKER_WEBUI_PORT=8089
# 從節點工作目錄
SPARK_WORKER_DIR=/spark/worker
# Options for launcher
# - SPARK_LAUNCHER_OPTS, to set config properties and Java options for the launcher (e.g. "-Dx=y")
# Generic options for the daemons used in the standalone deploy mode
# - SPARK_CONF_DIR Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - SPARK_LOG_DIR Where log files are stored. (Default: ${SPARK_HOME}/logs)
# - SPARK_LOG_MAX_FILES Max log files of Spark daemons can rotate to. Default is 5.
# - SPARK_PID_DIR Where the pid file is stored. (Default: /tmp)
# - SPARK_IDENT_STRING A string representing this instance of spark. (Default: $USER)
# - SPARK_NICENESS The scheduling priority for daemons. (Default: 0)
# - SPARK_NO_DAEMONIZE Run the proposed command in the foreground. It will not output a PID file.
# Options for native BLAS, like Intel MKL, OpenBLAS, and so on.
# You might get better performance to enable these options if using native BLAS (see SPARK-21305).
# - MKL_NUM_THREADS=1 Disable multi-threading of Intel MKL
# - OPENBLAS_NUM_THREADS=1 Disable multi-threading of OpenBLAS
# spark 的日志目錄
SPARK_LOG_DIR=/spark/logs
相關的配置項說明在這Spark Standalone Mode - Spark 3.2.0 Documentation (apache.org)
然後配置workers
接着把這個配置好的檔案分發到hadoop02的
/spark目錄下
使用
scp -r /spark hadoop02:/spark/
然後在hadoop01上啟動(當然如果你在hadoop01上配置那麼就可以不需要從hadoop02分發到hadoop01上了)
别忘記
SPARK_HOME
接着切換到
/spark/sbin
目錄下,執行啟動腳本
啟動過程中提示hadoop03還沒有相關檔案
并且提示我們hadoop02的JAVA_HOME沒有設定,這就離譜了,我們第一個配置的就是JAVA_HOME。
錯誤資訊中給出了啟動worker的指令,我們直接拷貝到hadoop02上執行看下,發現是可以執行的
接着我們在界面中看下
到worker裡面看下,發現和我們配置的一樣
檢視java程序,發現master已經在hadoop01上啟動
hadoop02啟動的是worker
現在剩下一個問題,在hadoop01上啟動,無法啟動hadoop02的worker
我們檢視下
start-all.sh
實際上
start-all.sh
啟動了
start-master.sh
和
start-worker.sh
重點看
start-worker.sh
可以看到會執行這兩個腳本,用于處理配置,我們把java環境在
spark-config.sh
中設定下
然後重新分發并重新開機
就沒有報錯,并且在hadoop02上成功啟動了
将
/spark
目錄分發到hadoop03并進行重新開機
需要注意,我們配置主節點和從節點的通信端口都是4040,界面通路端口都是8089。這也就是意味着,如果要在hadoop01上啟動Worker ,會出現端口沖突。
不過我們有三個節點,本來就是打算讓hadoop01成為master的。
開發環境內建
我們開發spark程式是在idea中開發的,是以需要在idea中安裝scala的插件
然後在windows環境中配置scala的環境
先把scala的檔案從hadoop01上下載下傳到windows中
然後配置環境變量
接着在idea中指定scala的sdk
idea會自動掃描全部的scala的sdk
選擇scala目錄
有了scala就可以建立spark的項目了,不過還需要scala的打包建構工具,也就是sbt
我們到sbt - The interactive build tool (scala-sbt.org)下載下傳sbt
下載下傳zip包就行
将下載下傳的sbt解壓到windows中,并配置環境變量
繼續建立項目(scala版本最好選擇和我們編譯spark相同的版本,當然建立好項目後在修改也是可以的)
根據上面這兩篇資料的内容,簡單了解下sbt。不希望能做到更好,隻要能正常使用就行了。
然後重新打開idea,打開建立的studyspark項目
sbt就開始下載下傳依賴了
此時sbt附加元件目會異常的,因為idea預設使用自己的sbt,而不是我們安裝的sbt
需要在settings中設定我們自己的sbt
重新建構項目
會彈出sbt-shell(如果你沒有把sbt配置的全部勾選,那麼應該是不會彈出sbt-shell的,那麼這步可以跳過)
/*************************跳過開始*****************************
如果你也出現上述提示,那麼表示在等待sbt使用預設的配置,從國外下載下傳依賴
我們在sbt的配置中增加如下參數
-Dsbt.override.build.repos=true
-Dsbt.repository.config=E:\sbt\conf\repo.properties
第一行表示使用全局的倉庫配置
第二行則是指定使用的倉庫配置的檔案路徑
記得取消這個勾選
然後重新導入項目
這裡發現sbt-shell中文亂碼了,很可惜,我嘗試了網上找到的方式,都失敗了,或許你可以試試,在sbt的參數中增加
-Dfile.encoding=UTF-8
-Dconsold.encoding=GBK
對我都是無效的😆
********************跳過結束*********************
此時項目結構如下
項目相關的配置可以在
build.sbt
中修改
build.sbt如下
// 建立配置,用于辨別spark的版本
lazy val sparkVersion = SettingKey[String]("spark-version", "spark version")
// 定義公共的包基礎
lazy val packageBaseDir = SettingKey[String]("package-base-dir", "package base dir")
// 抽取公共配置
lazy val commonSettings = Seq(
version := "1.0",
scalaVersion := "2.12.15",
sbtVersion := "1.6.1",
sparkVersion := "3.2.0",
// 定義項目包基礎
packageBaseDir := "com.study.spark",
// 定義每個項目的包字首
ThisProject / idePackagePrefix := Some(packageBaseDir.value + "." + name.value),
// 定義輸出目錄
ideOutputDirectory := Some(file("target")),
organization := "com.study.spark",
// 申明源碼路徑
sourceDirectories := Seq(
file("src/main/scala"),
file("src/main/java"),
file("src/test/scala"),
file("src/test/java")
),
// 聲明資源路徑
resourceDirectories := Seq(
file("src/main/resources"),
file("src/test/resources")
),
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-sql" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-mllib" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-streaming" % sparkVersion.value % "provided"
)
)
// 設定根項目
lazy val root = (project in file("."))
.settings(
name := "studyspark",
commonSettings
)
接着我們把
sbt-assembly
插件加入到項目中
等待sbt重新整理項目後如下
因為我們在根項目中是不會進行任何編碼的,是以我們可以将根項目中的src目錄删除
最終我們的代碼是需要上傳到git的,是以建立
.gitignore
檔案,将
target/
排除
我們建立一個helloworld的子產品,驗證sbt下的scala是否可用
别忘記在build.sbt中定義helloworld項目
lazy val helloworld = (project in file("helloworld"))
.settings(
name := "helloworld",
commonSettings
)
别忘記建立目錄
main,test,scala,java,resources
等.
建立好後,我們建立如下代碼
在運作之前需要先執行compile操作
你可以在sbt-shell中執行
也可以在sbt工具視窗中執行(我比較喜歡在sbt-shell中操作)
點選運作
但是現在我們隻是能在ide中執行,我們的目的是打出jar包的,是以我們還需要配置
sbt-assembly
插件
打開
sbt-assembly
插件的官方文檔sbt-assembly (scala-lang.org)
lazy val helloworld = (project in file("helloworld"))
.settings(
name := "helloworld",
// 定義主類
assembly / mainClass := Some(idePackagePrefix.value.get + ".Hello"),
// 定義jar包名 項目名字_scala版本_項目版本.jar
assembly / assemblyJarName := name.value + "_" + scalaVersion.value + "_" + version.value + ".jar",
// 依賴合并政策
assemblyMergeStrategy := {
case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
case "application.conf" => MergeStrategy.concat
case "unwanted.txt" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
commonSettings
)
然後執行
assembly
操作,記得切換到helloworld項目下,你也可以在sbt工具視窗執行
不過我喜歡在sbt-shell中執行
然後耐心的等待吧,第一次會比較慢的
可以看到jar包已經生成了
我們使用java -jar 執行
可以看到打出來的jar包成功執行了
到了這裡scala和sbt都內建完畢,此時需要內建spark了
spark的依賴下載下傳會比較慢,而且新版本國内的鏡像不一定有,需要從國外下載下傳
這裡推薦一個Idea的插件:
Big Data Tools
它可以連接配接到遠端的hadoop,hdfs,spark的叢集上,用于監控和互動,安裝後重新開機idea會在下面出現工具視窗
支援的還是挺多的
首先我們将插件和hadoop01進行連接配接
展示如下,和我們在浏覽器檢視的效果是一樣的
連接配接hdfs,需要注意,填寫的hdfs的通信位址,不是浏覽器位址
連接配接上之後,就可以像操作idea裡面的項目檔案一樣,上傳下載下傳等
接着我們啟動hadoop01的spark叢集
可惜了,連接配接spark還是有問題
到了這裡,工具裝了一大堆,但是還沒開始開發一行代碼,接下來就開發一個小案例wordcount試試吧
當我們安裝了
big data tools
插件後,建立module的時候,就可以選擇
big data tools
下的
spark
模闆了
這是一個類似于spring boot的模闆,裡面自動建立一些檔案和配置,更準确應該是和maven比較像
加載完畢後項目結構如下
不過這種建立項目隻是适合建立頂級項目,對于子級項目是不行的,會覆寫已有的build.sbt等資訊的
我們前面已經搭建好了頂級項目,是以我們可以直接建立一個scala項目就行了
然後指定子產品的名字就行了
我們做個優化,把打包的配置,除了主類之外的,都放在公共配置中
// 抽取公共配置
lazy val commonSettings = Seq(
version := "1.0",
scalaVersion := "2.12.15",
sbtVersion := "1.6.1",
sparkVersion := "3.2.0",
// 定義項目包基礎
packageBaseDir := "com.study.spark",
// 定義每個項目的包字首
ThisProject / idePackagePrefix := Some(packageBaseDir.value + "." + name.value),
// 定義輸出目錄
ideOutputDirectory := Some(file("target")),
organization := "com.study.spark",
// 申明源碼路徑
sourceDirectories := Seq(
file("src/main/scala"),
file("src/main/java"),
file("src/test/scala"),
file("src/test/java")
),
// 聲明資源路徑
resourceDirectories := Seq(
file("src/main/resources"),
file("src/test/resources")
),
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-sql" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-mllib" % sparkVersion.value % "provided",
"org.apache.spark" %% "spark-streaming" % sparkVersion.value % "provided"
),
// 定義jar包名 項目名字_scala版本_項目版本.jar
ThisProject / assembly / assemblyJarName := name.value + "_" + scalaVersion.value + "_" + version.value + ".jar",
// 依賴合并政策
ThisBuild / assemblyMergeStrategy := {
case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
case "application.conf" => MergeStrategy.concat
case "unwanted.txt" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
然後把wordcount加入到根項目中,主類先不寫
lazy val wordcount = (project in file("wordcount"))
.settings(
name := "wordcount",
commonSettings
)
然後建立目錄
我們在scala目錄下建立主類
首先使用本地進行執行
package com.study.spark.wordcount
import org.apache.spark.{SparkConf, SparkContext}
object WordCount {
def main(args: Array[String]): Unit = {
// 配置spark
val conf = new SparkConf().setMaster("local").setAppName("wordcount")
// 擷取sparkContext
val sc = new SparkContext(conf)
// 從本地讀取檔案,其實就是 build.sbt 檔案
sc.textFile("file:///E:\\java\\studyspark\\build.sbt")
// 按照空格拆分
.flatMap(_.split(" "))
// 每個單詞計數1
.map(_->1)
// 按單詞分組統計
.reduceByKey(_+_)
// 計算
.collect()
// 列印統計結果
.foreach(println(_))
}
}
别忘記在build.sbt中配置主類(之前我們隻有一個有效的項目,是以我們可以直接取參數的值,但是現在有了多個項目,就需要在取值的時候,限定作用域)
需要注意的是我們配置的spark依賴是不會在classpath中使用的,因為預設當我們把spark任務的jar包送出給spark叢集的時候,spark相關的依賴是不需要在jar包中的。
但是當我們需要本地啟動的時候,如果沒有spark的依賴,會導緻spark相關的類找不到。
是以我們需要在本地啟動的spark項目中,再次配置spark的依賴,差別是不寫後面
provided
作用域
然後切換到wordcount項目下,編譯并執行(使用run執行,而不是使用ide的執行按鈕)
如果一切順利,就會看到執行的日志
我們也可以打包的,使用
sbt-assembly
打包
然後使用java -jar 執行
執行會抛出異常,這是因為缺少了scala的依賴,我們需要把scala的依賴也加入
我們要求scala的依賴每次都必須打包,注意這裡是一個
%
打出來的jar包不能簡單的使用java -jar 運作,需要送出到spark環境中執行,因為我們在打包的時候并沒有打spark相關的包。
為了能在伺服器上使用,我們需要修改我們的代碼,需要連結到伺服器上的spark叢集,而且讀取檔案應該讀取hdfs中的檔案
首先啟動伺服器上的hdfs服務
接着啟動spark叢集
接着把build.sbt上傳到hdfs中
然後重新打包,并上傳到伺服器
然後使用
spark-submit --master spark://hadoop01:4040 --class com.study.spark.wordcount.WordCount /spark/wordcount_2.12.15_1.0.jar
送出到spark叢集,spark就開始執行了
執行結果和在本地執行一模一樣
而且在spark的界面中也能看到
如果你還沒有配置曆史服務,那麼會啟動報錯的,提示沒有配置曆史資料存儲路徑
拷貝spark-defaults.conf.template為spark-defaults.conf
内容如下
# 是否開啟日志
spark.eventLog.enabled true
# 日志存儲路徑
spark.eventLog.dir hdfs://hadoop01:8020/logs
# 是否壓縮
spark.eventLog.compress true
同時在spark-env.sh中配置以下内容
# 配置spark曆史服務
SPARK_HISTORY_OPTS="-Dspark.history.ui.port=8086 -Dspark.history.retainedApplications=10 -Dspark.history.fs.logDirectory=hdfs://hadoop01:8020/logs"
我們啟動spark曆史記錄服務
啟動曆史記錄服務不報錯
而且界面能打開
然後在執行一次,因為我們的任務很快,是以開啟曆史記錄,友善我們檢視job的資訊
這時候就能在曆史界面檢視了
我們實際上是想看看dag圖
很漂亮。
源碼編譯的3.2.0版本無法在window上直接用spark-shell啟動
這個問題我發現不僅僅是我們自己編譯的二進制分發包有問題,就連官方下載下傳的二進制分發包,如果你直接使用
spark-shell
啟動,也是會無法啟動的。這個問題困擾我了好幾天,我在網上找到了相關的讨論windows - Spark illegal character in path - Stack Overflow
但是也沒給出問題原因和解決方案,隻是說降低版本。
之前是源碼編譯二進制包,是以我也有spark的源碼,我自己根據堆棧找了好長時間,奈何自己水準太低,從源碼中沒有找到相關的代碼。
不過從我自己了解的源碼來看,目前好像就是
spark-shell
的repl的啟動存在問題,而送出作業等貌似是沒有問題的。(我自己沒試)
總結
整個文章的過程比較坎坷,比較艱難。
我是在看書的時候,知道了scala的sdk的二進制不相容,以及目前市面上大多數公司使用cdh的發行包,于是我也到cdh的官網想下載下傳最新的發行包,結果發現cdh版本貌似也開始收費了,于是隻能自己使用源碼編譯。
源碼編譯時第一個階段,當我曆經艱難,完成了源碼編譯後,發現不能在windows上使用spark-shell,于是在伺服器叢集上搭建spark叢集。這裡主要學習了spark的啟動和叢集部署,以及一些配置等。
上述這兩個階段基本上是年前完成的,在搭建spark開發環境的時候,才知道spark或者說scala推薦使用sbt編譯工具,可憐我maven都不是很熟悉,我哪會sbt呢。沒轍,想辦法研究sbt,研究sbt剛開始,就過年了,中間大概10多天吧,就是啥都沒做,新年開工,才開始正式研究sbt,研究幾天,基本上做到了入門,就繼續在idea中內建spark,sbt環境。
最終實作了idea本地開發編碼,本地在sbt的基礎上執行,然後打包送出到spark叢集中執行。