Spark 安裝部署與快速上手

uiuing發表於2021-11-18

Spark 介紹

核心概念

Spark 是 UC Berkeley AMP lab 開發的一個叢集計算的框架,類似於 Hadoop,但有很多的區別。

最大的優化是讓計算任務的中間結果可以儲存在記憶體中,不需要每次都寫入 HDFS,更適用於需要迭代的 MapReduce 演算法場景中,可以獲得更好的效能提升。

例如一次排序測試中,對 100TB 資料進行排序,Spark 比 Hadoop 快三倍,並且只需要十分之一的機器。

Spark 叢集目前最大的可以達到 8000 節點,處理的資料達到 PB 級別,在網際網路企業中應用非常廣泛。

Spark 的特性

Hadoop 的核心是分散式檔案系統 HDFS 和計算框架 MapReduces。Spark 可以替代 MapReduce,並且相容 HDFS、Hive 等分散式儲存層,良好的融入 Hadoop 的生態系統。

Spark 執行的特點

  • 中間結果輸出:Spark 將執行工作流抽象為通用的有向無環圖執行計劃(DAG),可以將多 Stage 的任務串聯或者並行執行。
  • 資料格式和記憶體佈局:Spark 抽象出分散式記憶體儲存結構彈性分散式資料集 RDD,能夠控制資料在不同節點的分割槽,使用者可以自定義分割槽策略。
  • 任務排程的開銷:Spark 採用了事件驅動的類庫 AKKA 來啟動任務,通過執行緒池的複用執行緒來避免系統啟動和切換開銷。

Spark 的優勢

  • 速度快,執行工作負載快 100 倍。Apache Spark 使用最先進的 DAG 排程器、查詢優化器和物理執行引擎,實現了批處理和流資料的高效能。
  • 易於使用,支援用 Java、Scala、Python、R 和 SQL 快速編寫應用程式。Spark 提供了超過 80 個運算元,可以輕鬆構建並行應用程式。您可以從 Scala、Python、R 和 SQL shell 中互動式地使用它。
  • 普遍性,結合 SQL、流處理和複雜分析。Spark 提供了大量的庫,包括 SQLDataFrames、用於機器學習的 MLlib、GraphXSpark 流。您可以在同一個應用程式中無縫地組合這些庫。
  • 各種環境都可以執行,Spark 在 Hadoop、Apache Mesos、Kubernetes、單機或雲主機中執行。它可以訪問不同的資料來源。您可以使用它的獨立叢集模式在 EC2、Hadoop YARN、Mesos 或 Kubernetes 上執行 Spark。訪問 HDFS、Apache Cassandra、Apache HBase、Apache Hive 和數百個其他資料來源中的資料。

哪些公司在使用 Spark

日常為我們所熟知的,在國外就有 IBM Almaden(IBM 研究實驗室)、Amazon(亞馬遜)等,而在國內有 baidu(百度)、Tencent(騰訊)等等,包括一些其它的公司大部分都使用 Spark 來處理生產過程中產生的大量資料。更多詳情可以參考連結: 誰在使用 Spark?

2.3 Spark 生態系統 BDAS

目前,Spark 已經發展成為包含眾多子專案的大資料計算平臺。

BDAS 是伯克利大學提出的基於 Spark 的資料分析棧(BDAS)。

其核心框架是 Spark,同時涵蓋支援結構化資料 SQL 查詢與分析的查詢引擎 Spark SQL,提供機器學習功能的系統 MLBase 及底層的分散式機器學習庫 MLlib,並行圖計算框架 GraphX,流計算框架 Spark Streaming,近似查詢引擎 BlinkDB,記憶體分散式檔案系統 Tachyon,資源管理框架 Mesos 等子專案。這些子專案在 Spark 上層提供了更高層、更豐富的計算正規化

image-20211020112105076



部署前準備

Spark 安裝非常簡單,簡單到只需要下載 binary 包解壓即可

安裝 Spark 之前需要先安裝 Java,Scala 及 Python。

  • java 1.8.0
  • scala 2.11.8
  • python 2.7

安裝Java

下載

參考清華軟體源:https://mirrors.tuna.tsinghua.edu.cn/AdoptOpenJDK/

wget wget https://mirrors.tuna.tsinghua.edu.cn/AdoptOpenJDK/8/jdk/x64/linux/OpenJDK8U-jdk_x64_linux_openj9_linuxXL_8u282b08_openj9-0.24.0.tar.gz --no-check-certificate

按需求下載之後記得修改環境變數

image-20211020144350213

記得配置環境變數,新增 bin 目錄即可


安裝Scala

下載

wget https://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz

image-20211020144410232

記得配置環境變數,新增 bin 目錄即可


安裝Python

一般系統自帶 Python2

python --version

如果需要 Python3 可以自行下載

yum -y install python3

image-20211020144327257


Spark 下載

此處使用的是:Spark 2.4.8

官網上下載已經預編譯好的 Spark binary,直接解壓即可。

Spark 官方下載連結:http://spark.apache.org/downloads.html

下載

參考

wget https://mirrors.huaweicloud.com/apache/spark/spark-2.4.4/spark-2.4.4-bin-hadoop2.7.tgz

記得配置環境變數,新增 bin 目錄即可

image-20211020144631459



部署模式介紹

Spark on Mesos未嘗試過,大家可以自行嘗試

本文僅介紹 Standalone 模式和 Spark on Yarn模式

按照自己需求配合!

本地模式

Spark單機執行,直接解壓執行start-all.sh即可,一般用於開發測試


Standalone 模式

構建一個由Master+Slave構成的Spark叢集,Spark執行在叢集中。

獨立模式,自帶完整的服務,可單獨部署到一個叢集中,無需依賴任何其他資源管理系統。

從一定程度上說,該模式是其他兩種的基礎。借鑑 Spark 開發模式,我們可以得到一種開發新型計算框架的一般思路:先設計出它的 standalone 模式,為了快速開發,起初不需要考慮服務(比如 master/slave)的容錯性,之後再開發相應的 wrapper,將 stanlone 模式下的服務原封不動的部署到資源管理系統 yarn 或者 mesos 上,由資源管理系統負責服務本身的容錯。目前 Spark 在 standalone 模式下是沒有任何單點故障問題的,這是藉助 zookeeper 實現的,思想類似於 Hbase master 單點故障解決方案。將 Spark standalone 與 MapReduce 比較,會發現它們兩個在架構上是完全一致的:

  1. 都是由 master/slaves 服務組成的,且起初 master 均存在單點故障,後來均通過 zookeeper 解決(Apache MRv1 的 JobTracker 仍存在單點問題,但 CDH 版本得到了解決);
  2. 各個節點上的資源被抽象成粗粒度的 slot,有多少 slot 就能同時執行多少 task。不同的是,MapReduce 將 slot 分為 map slot 和 reduce slot,它們分別只能供 Map Task 和 Reduce Task 使用,而不能共享,這是 MapReduce 資源利率低效的原因之一,而 Spark 則更優化一些,它不區分 slot 型別,只有一種 slot,可以供各種型別的 Task 使用,這種方式可以提高資源利用率,但是不夠靈活,不能為不同型別的 Task 定製 slot 資源。總之,這兩種方式各有優缺點。

Spark on Yarn 模式

Spark客戶端直接連線Yarn。不需要額外構建Spark叢集。

這是一種很有前景的部署模式。但限於 YARN 自身的發展,目前僅支援粗粒度模式(Coarse-grained Mode)。

這是由於 YARN 上的 Container 資源是不可以動態伸縮的,一旦 Container 啟動之後,可使用的資源不能再發生變化,不過這個已經在 YARN 計劃中了。

spark on yarn 的支援兩種模式:

  1. yarn-cluster:適用於生產環境
  2. yarn-client:適用於互動、除錯,希望立即看到 app 的輸出

yarn-cluster 和 yarn-client 的區別在於 yarn appMaster,每個 yarn app 例項有一個 appMaster 程式,是為 app 啟動的第一個 container;負責從 ResourceManager 請求資源,獲取到資源後,告訴 NodeManager 為其啟動 container。

yarn-cluster 和 yarn-client 模式內部實現還是有很大的區別。

如果你需要用於生產環境,那麼請選擇 yarn-cluster;而如果你僅僅是 Debug 程式,可以選擇 yarn-client。


Spark on Mesos 模式

Spark客戶端直接連線Mesos。不需要額外構建Spark叢集。

這是很多公司採用的模式,官方推薦這種模式(當然,原因之一是血緣關係)。

正是由於 Spark 開發之初就考慮到支援 Mesos,因此,目前而言,Spark 執行在 Mesos 上會比執行在 YARN 上更加靈活,更加自然。

目前在 Spark On Mesos 環境中,使用者可選擇兩種排程模式之一執行自己的應用程式(可參考 Andrew Xia 的“Mesos Scheduling Mode on Spark”):

  1. 粗粒度模式(Coarse-grained Mode):每個應用程式的執行環境由一個 Dirver 和若干個 Executor 組成,其中,每個 Executor 佔用若干資源,內部可執行多個 Task(對應多少個“slot”)。

    應用程式的各個任務正式執行之前,需要將執行環境中的資源全部申請好,且執行過程中要一直佔用這些資源,即使不用,最後程式執行結束後,回收這些資源。舉個例子,比如你提交應用程式時,指定使用 5 個 executor 執行你的應用程式,每個 executor 佔用 5GB 記憶體和 5 個 CPU,每個 executor 內部設定了 5 個 slot,則 Mesos 需要先為 executor 分配資源並啟動它們,之後開始排程任務。

    另外,在程式執行過程中,mesos 的 master 和 slave 並不知道 executor 內部各個 task 的執行情況,executor 直接將任務狀態通過內部的通訊機制彙報給 Driver,從一定程度上可以認為,每個應用程式利用 mesos 搭建了一個虛擬叢集自己使用。

  2. 細粒度模式(Fine-grained Mode):鑑於粗粒度模式會造成大量資源浪費,Spark On Mesos 還提供了另外一種排程模式:細粒度模式,這種模式類似於現在的雲端計算,思想是按需分配。

    與粗粒度模式一樣,應用程式啟動時,先會啟動 executor,但每個 executor 佔用資源僅僅是自己執行所需的資源,不需要考慮將來要執行的任務,之後,mesos 會為每個 executor 動態分配資源,每分配一些,便可以執行一個新任務,單個 Task 執行完之後可以馬上釋放對應的資源。

    每個 Task 會彙報狀態給 Mesos slave 和 Mesos Master,便於更加細粒度管理和容錯,這種排程模式類似於 MapReduce 排程模式,每個 Task 完全獨立,優點是便於資源控制和隔離,但缺點也很明顯,短作業執行延遲大。


總結

這三種分散式部署方式各有利弊,通常需要根據實際情況決定採用哪種方案。

進行方案選擇時,往往要考慮公司的技術路線(採用 Hadoop 生態系統還是其他生態系統)、相關技術人才儲備等。上面涉及到 Spark 的許多部署模式,究竟哪種模式好這個很難說,需要根據你的需求,如果你只是測試 Spark Application,你可以選擇 local 模式。而如果你資料量不是很多,Standalone 是個不錯的選擇。當你需要統一管理叢集資源(Hadoop、Spark 等),那麼你可以選擇 Yarn 或者 mesos,但是這樣維護成本就會變高。

  • 從對比上看,mesos 似乎是 Spark 更好的選擇,也是被官方推薦的
  • 但如果你同時執行 hadoop 和 Spark,從相容性上考慮,Yarn 是更好的選擇。
  • 如果你不僅執行了 hadoop,spark。還在資源管理上執行了 docker,Mesos 更加通用。
  • Standalone 對於小規模計算叢集更適合!

Standalone 模式

Standalone 另可分兩種子模式:

  • 單機
  • 叢集

當然,叢集部署的前提是單機的部署完成,根據自己的需求調整即可

單機部署

Spark 雖然是大規模的計算框架,但也支援在單機上執行

修改配置檔案

進入 Spark 配置目錄

cd $SPARK_HOME/conf

日誌配置

建立/複製

cp log4j.properties.template log4j.properties

我們修改 log4j.rootCategory 的 「INFO」修改為「WARN」,這一步是修改日誌等級,可避免測試中輸出太多資訊

image-20211020144932483


spark-env.sh

建立/複製

 cp spark-env.sh.template spark-env.sh

新增HOME 變數:JAVA_HOME、SPARK_HOME、SCALA_HOME

考慮我們已經新增至環境變數檔案裡了,所以我們重新整理配置的環境檔案即可

image-20211020145303859

spark-env.sh指令碼會在啟動 Spark 時載入,內容包含很多配置選項及說明,在以後會用到少部分,感興趣可以仔細閱讀這個檔案的註釋內容。


啟動Spark 服務

這一節將啟動 Spark 的 master 主節點和 slave 從節點

也會介紹 spark 單機模式和叢集模式的部署區別

啟動主節點

前往sbin 目錄

cd $SPARK_HOME/sbin

啟動

./start-master.sh

image-20211020153210847

沒有報錯的話表示 master 已經啟動成功

master 預設可以通過 web 訪問http://localhost:8080

image-20211020153335079

圖中所示,master 中暫時還沒有一個 worker ,我們啟動 worker 時需要 master 的引數,該引數已經在上圖中標誌出來:spark://master:7077,請在執行後續命令時替換成你自己的引數。


啟動從節點

啟動 slave

./start-slave.sh spark://master:7077

image-20211020153601603

沒有報錯表示啟動成功,再次重新整理瀏覽器頁面可以看到下圖所示新的 worker 已經新增

也可以用jps命令檢視啟動的服務,應該會列出MasterWorker

image-20211020153725807


測試例項

使用 spark-shell 連線 master ,注意把 MASTER 引數替換成你實驗環境中的實際引數

MASTER=spark://master:7077 spark-shell

image-20211020154059684

重新整理 master 的 web 頁面,可以看到新的Running Applications,如下圖所示:

image-20211020154046855

當退出 spark-shell 時,這個 application 會移動到Completed Applications一欄。

image-20211020154219423

可以自己點選頁面中的 Application 和 Workers 的連結檢視並瞭解相關資訊。


停止服務

停止服務的指令碼為./sbin/stop-all.sh

./stop-all.sh

但我建議依次關閉

./stop-master.sh
./stop-slave.sh

image-20211020154523357

通過 jps 可以看到,master 與 worker 程式都已經停止


叢集部署

修改配置

在 「單機模式」小節下的「修改配置檔案」的基礎上進行新增/修改

進入 Spark 配置目錄

cd $SPARK_HOME/conf

spark-env.sh

引數解讀:

  • SPARK_MASTER_HOST = Master的主機名
  • SPARK_MASTER_PORT = 提交Application的埠,預設7077,可更改
  • SPARK_WORKER_CORES = 每一個Worker最多可以使用的cpu核個數
  • SPARK_WORKER_MEMORY = 每個Worker最多可以使用的記憶體

其實你完全可以參考spark-env.sh內的註釋

編輯

vim spark-env.sh

做出如下修改(位置非固定):

image-20211020164337172


slaves

修改 slaves 配置檔案,新增 Worker 的主機列表

複製/建立

cp slaves.template slaves

修改localhost 為你需要的機器的 HostName

你可以參考我的:

image-20211020164928409

注意需要先把所有主機名輸入到 /etc/hosts 避免無法解析


同步

此處不進行贅述,具體操作大家自行搜尋,或者檢視我關於 「HADOOP部署」的相關文章

大致操作如下:

  • ssh-keygen 命令配合 ssh-copy-id 命令實現ssh免密
  • scp 命令同步所有設定(指Spark 下的 conf 檔案下,或者同步 Spark 檔案)

啟動叢集

前往 master 機器下執行

前往sbin 目錄

cd $SPARK_HOME/sbin

在這臺機啟動叢集

./start-all.sh

啟動的步驟和「單機部署」下的「啟動 Spark 服務」一致,關閉也一致

start-all.sh 和 start-master.sh、start-slave.sh 和 Hadoop 裡的 start-all.sh、start-yarn.sh、start-dfs.sh關係大致一樣

image-20211020171129076

Web:

image-20211020171210590



Spark 互動式執行

Spark-Shell

Spark-Shell是 Spark 自帶的一個 Scala 互動 Shell ,可以以指令碼方式進行互動式執行,類似直接用 Python 及其他指令碼語言的 Shell 。

進入Spark-Shell只需要執行spark-shell即可:

spark-shell

(前提是你配置好了 Spark 的環境變數)

image-20211020150911321

進入到Spark-Shell後可以使用Ctrl D組合鍵退出 Shell。

Spark-Shell中我們可以使用 Scala 的語法進行簡單的測試,比如我們執行下面幾個語句獲得檔案/etc/protocols的行數以及第一行的內容:

var f = sc.textFile("/etc/protocols")
f.count()
f.first()

image-20211020151401362

上面的操作中建立了一個 RDD file,執行了兩個簡單的操作:

  • count()獲取 RDD 的行數
  • first()獲取第一行的內容

我們繼續執行其他操作,比如查詢有多少行含有tcpudp字串:

f.filter(line => line.contains("tcp")).count()
f.filter(line => line.contains("udp")).count()

image-20211020151514149

檢視一共有多少個不同單詞的方法,這裡用到 Mapreduce 的思路:

var wordcount = file.flatMap(line => line.split(" ")).map(word => (word,1)).reduceByKey(_+_)

wordcount.count()

image-20211020151832475

上面兩步驟我們發現,/etc/protocols中各有一行含有tcpudp字串,並且一共有 442 個不同的單詞。

上面每個語句的具體含義這裡不展開,可以結合你閱讀的文章進行理解,這裡僅僅提供一個簡單的例子讓大家對 Spark 運算有基本認識。

操作完成後,Ctrl D組合鍵退出 Shell。


Pyspark

Pyspark 類似 Spark-Shell ,是一個 Python 的互動 Shell 。

執行pyspark啟動進入 Pyspark:

pyspark

image-20211020152102799

退出方法仍然是Ctrl D組合鍵。

在 Pyspark 中,我們可以用 Python 語法執行 Spark-Shell 中的操作,比如下面的語句獲得檔案/etc/protocols 的行數以及第一行的內容:

file = sc.textFile("/etc/protocols")
file.count()
file.first()

image-20211020152146802

操作完成後,Ctrl D組合鍵退出 Shell。

對於 Pyspark 大家可以自行學習擴充,可以參考官方文件Spark Python API



提交應用程式

在Spark bin目錄下的spark-submit可以用來在叢集上啟動應用程式。它可以通過統一的介面使用Spark支援的所有叢集管理器 ,所有你不必為每一個管理器做相應的配置。

用spark-submit啟動應用程式

bin/spark-submit指令碼負責建立包含Spark以及其依賴的類路徑(classpath),它支援不同的叢集管理器以及Spark支援的載入模式。

./bin/spark-submit \
  --class <main-class>
  --master <master-url> \
  --deploy-mode <deploy-mode> \
  --conf <key>=<value> \
  ... # other options
  <application-jar> \
  [application-arguments]

一些常用的選項是:

  • --class:你的應用程式的入口點(如org.apache.spark.examples.SparkPi)
  • --master:叢集的master URL(如spark://23.195.26.187:7077)
  • --deploy-mode:在worker節點部署你的driver(cluster)或者本地作為外部客戶端(client)。預設是client。
  • --conf:任意的Spark配置屬性,格式是key=value。
  • application-jar:包含應用程式以及其依賴的jar包的路徑。這個URL必須在叢集中全域性可見,例如,存在於所有節點的hdfs://路徑或file://路徑
  • application-arguments:傳遞給主類的主方法的引數

一個通用的部署策略是從閘道器叢集提交你的應用程式,這個閘道器機器和你的worker叢集物理上協作。在這種設定下,client模式是適合的。在client模式下,driver直接在spark-submit程式 中啟動,而這個程式直接作為叢集的客戶端。應用程式的輸入和輸出都和控制檯相連線。因此,這種模式特別適合涉及REPL的應用程式。

另一種選擇,如果你的應用程式從一個和worker機器相距很遠的機器上提交,通常情況下用cluster模式減少drivers和executors的網路遲延。注意,cluster模式目前不支援獨立叢集、 mesos叢集以及python應用程式。

有幾個我們使用的叢集管理器特有的可用選項。例如,在Spark獨立叢集的cluster模式下,你也可以指定--supervise用來確保driver自動重啟(如果它因為非零退出碼失敗)。 為了列舉spark-submit所有的可用選項,用--help執行它。

# Run application locally on 8 cores
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master local[8] \
  /path/to/examples.jar \
  100

# Run on a Spark Standalone cluster in client deploy mode
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://master:7077 \
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a Spark Standalone cluster in cluster deploy mode with supervise
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://master:7077 \
  --deploy-mode cluster
  --supervise
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a YARN cluster
export HADOOP_CONF_DIR=XXX
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master yarn-cluster \  # can also be `yarn-client` for client mode
  --executor-memory 20G \
  --num-executors 50 \
  /path/to/examples.jar \
  1000

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

Master URLs

傳遞給Spark的url可以用下面的模式

Master URL Meaning
local 用一個worker執行緒本地執行Spark
local[K] 用k個worker執行緒本地執行Spark(理想情況下,設定這個值為你的機器的核數)
local[*] 用盡可能多的worker執行緒本地執行Spark
spark://HOST:PORT 連線到給定的Spark獨立部署叢集master。埠必須是master配置的埠,預設是7077
mesos://HOST:PORT 連線到給定的mesos叢集
yarn-client client模式連線到Yarn叢集。群集位置將基於通過HADOOP_CONF_DIR變數找到
yarn-cluster cluster模式連線到Yarn叢集。群集位置將基於通過HADOOP_CONF_DIR變數找到


Spark Standalone擴充套件

手動啟動叢集

你能夠通過下面的方式啟動獨立的master伺服器。

./sbin/start-master.sh

一旦啟動,master將會為自己列印出spark://HOST:PORT URL,你能夠用它連線到workers或者作為"master"引數傳遞給SparkContext。你也可以在master web UI上發現這個URL, master web UI預設的地址是http://localhost:8080

相同的,你也可以啟動一個或者多個workers或者將它們連線到master。

./bin/spark-class org.apache.spark.deploy.worker.Worker spark://IP:PORT

一旦你啟動了一個worker,檢視master web UI。你可以看到新的節點列表以及節點的CPU數以及記憶體。

下面的配置引數可以傳遞給master和worker。

Argument Meaning
-h HOST, --host HOST 監聽的主機名
-i HOST, --ip HOST 同上,已經被淘汰
-p PORT, --port PORT 監聽的服務的埠(master預設是7077,worker隨機)
--webui-port PORT web UI的埠(master預設是8080,worker預設是8081)
-c CORES, --cores CORES Spark應用程式可以使用的CPU核數(預設是所有可用);這個選項僅在worker上可用
-m MEM, --memory MEM Spark應用程式可以使用的記憶體數(預設情況是你的機器記憶體數減去1g);這個選項僅在worker上可用
-d DIR, --work-dir DIR 用於暫存空間和工作輸出日誌的目錄(預設是SPARK_HOME/work);這個選項僅在worker上可用
--properties-file FILE 自定義的Spark配置檔案的載入目錄(預設是conf/spark-defaults.conf)

叢集啟動指令碼

為了用啟動指令碼啟動Spark獨立叢集,你應該在你的Spark目錄下建立一個名為conf/slaves的檔案,這個檔案必須包含所有你要啟動的Spark worker所在機器的主機名,一行一個。如果 conf/slaves不存在,啟動指令碼預設為單個機器(localhost),這臺機器對於測試是有用的。注意,master機器通過ssh訪問所有的worker。在預設情況下,SSH是並行執行,需要設定無密碼(採用私有金鑰)的訪問。 如果你沒有設定為無密碼訪問,你可以設定環境變數SPARK_SSH_FOREGROUND,為每個worker提供密碼。

一旦你設定了這個檔案,你就可以通過下面的shell指令碼啟動或者停止你的叢集。

  • sbin/start-master.sh:在機器上啟動一個master例項
  • sbin/start-slaves.sh:在每臺機器上啟動一個slave例項
  • sbin/start-all.sh:同時啟動一個master例項和所有slave例項
  • sbin/stop-master.sh:停止master例項
  • sbin/stop-slaves.sh:停止所有slave例項
  • sbin/stop-all.sh:停止master例項和所有slave例項

注意,這些指令碼必須在你的Spark master執行的機器上執行,而不是在你的本地機器上面。

你可以在conf/spark-env.sh中設定環境變數進一步配置叢集。利用conf/spark-env.sh.template建立這個檔案,然後將它複製到所有的worker機器上使設定有效。下面的設定可以起作用:

Environment Variable Meaning
SPARK_MASTER_IP 繫結master到一個指定的ip地址
SPARK_MASTER_PORT 在不同的埠上啟動master(預設是7077)
SPARK_MASTER_WEBUI_PORT master web UI的埠(預設是8080)
SPARK_MASTER_OPTS 應用到master的配置屬性,格式是 "-Dx=y"(預設是none),檢視下面的表格的選項以組成一個可能的列表
SPARK_LOCAL_DIRS Spark中暫存空間的目錄。包括map的輸出檔案和儲存在磁碟上的RDDs(including map output files and RDDs that get stored on disk)。這必須在一個快速的、你的系統的本地磁碟上。它可以是一個逗號分隔的列表,代表不同磁碟的多個目錄
SPARK_WORKER_CORES Spark應用程式可以用到的核心數(預設是所有可用)
SPARK_WORKER_MEMORY Spark應用程式用到的記憶體總數(預設是記憶體總數減去1G)。注意,每個應用程式個體的記憶體通過spark.executor.memory設定
SPARK_WORKER_PORT 在指定的埠上啟動Spark worker(預設是隨機)
SPARK_WORKER_WEBUI_PORT worker UI的埠(預設是8081)
SPARK_WORKER_INSTANCES 每臺機器執行的worker例項數,預設是1。如果你有一臺非常大的機器並且希望執行多個worker,你可以設定這個數大於1。如果你設定了這個環境變數,確保你也設定了SPARK_WORKER_CORES環境變數用於限制每個worker的核數或者每個worker嘗試使用所有的核。
SPARK_WORKER_DIR Spark worker執行目錄,該目錄包括日誌和暫存空間(預設是SPARK_HOME/work)
SPARK_WORKER_OPTS 應用到worker的配置屬性,格式是 "-Dx=y"(預設是none),檢視下面表格的選項以組成一個可能的列表
SPARK_DAEMON_MEMORY 分配給Spark master和worker守護程式的記憶體(預設是512m)
SPARK_DAEMON_JAVA_OPTS Spark master和worker守護程式的JVM選項,格式是"-Dx=y"(預設為none)
SPARK_PUBLIC_DNS Spark master和worker公共的DNS名(預設是none)

注意,啟動指令碼還不支援windows。為了在windows上啟動Spark叢集,需要手動啟動master和workers。

SPARK_MASTER_OPTS支援一下的系統屬性:

Property Name Default Meaning
spark.deploy.retainedApplications 200 展示完成的應用程式的最大數目。老的應用程式會被刪除以滿足該限制
spark.deploy.retainedDrivers 200 展示完成的drivers的最大數目。老的應用程式會被刪除以滿足該限制
spark.deploy.spreadOut true 這個選項控制獨立的叢集管理器是應該跨節點傳遞應用程式還是應努力將程式整合到儘可能少的節點上。在HDFS中,傳遞程式是資料本地化更好的選擇,但是,對於計算密集型的負載,整合會更有效率。
spark.deploy.defaultCores (infinite) 在Spark獨立模式下,給應用程式的預設核數(如果沒有設定spark.cores.max)。如果沒有設定,應用程式總數獲得所有可用的核,除非設定了spark.cores.max。在共享叢集上設定較低的核數,可用防止使用者預設抓住整個叢集。
spark.worker.timeout 60 獨立部署的master認為worker失敗(沒有收到心跳資訊)的間隔時間。

SPARK_WORKER_OPTS支援的系統屬性:

Property Name Default Meaning
spark.worker.cleanup.enabled false 週期性的清空worker/應用程式目錄。注意,這僅僅影響獨立部署模式。不管應用程式是否還在執行,用於程式目錄都會被清空
spark.worker.cleanup.interval 1800 (30分) 在本地機器上,worker清空老的應用程式工作目錄的時間間隔
spark.worker.cleanup.appDataTtl 7 24 3600 (7天) 每個worker中應用程式工作目錄的保留時間。這個時間依賴於你可用磁碟空間的大小。應用程式日誌和jar包上傳到每個應用程式的工作目錄。隨著時間的推移,工作目錄會很快的填滿磁碟空間,特別是如果你執行的作業很頻繁。

連線一個應用程式到叢集中

為了在Spark叢集中執行一個應用程式,簡單地傳遞spark://IP:PORT URL到SparkContext

為了在叢集上執行一個互動式的Spark shell,執行一下命令:

./bin/spark-shell --master spark://IP:PORT

你也可以傳遞一個選項--total-executor-cores <numcores>去控制spark-shell的核數。


啟動Spark應用程式

spark-submit指令碼支援最直接的提交一個Spark應用程式到叢集。對於獨立部署的叢集,Spark目前支援兩種部署模式。在client模式中,driver啟動程式與 客戶端提交應用程式所在的程式是同一個程式。然而,在cluster模式中,driver在叢集的某個worker程式中啟動,只有客戶端程式完成了提交任務,它不會等到應用程式完成就會退出。

如果你的應用程式通過Spark submit啟動,你的應用程式jar包將會自動分發到所有的worker節點。對於你的應用程式依賴的其它jar包,你應該用--jars符號指定(如--jars jar1,jar2)。

另外,cluster模式支援自動的重啟你的應用程式(如果程式一非零的退出碼退出)。為了用這個特徵,當啟動應用程式時,你可以傳遞--supervise符號到spark-submit。如果你想殺死反覆失敗的應用, 你可以通過如下的方式:

./bin/spark-class org.apache.spark.deploy.Client kill <master url=""> <driver id="">

你可以在獨立部署的Master web UI(http://:8080)中找到driver ID。


資源排程

獨立部署的叢集模式僅僅支援簡單的FIFO排程器。然而,為了允許多個並行的使用者,你能夠控制每個應用程式能用的最大資源數。在預設情況下,它將獲得叢集的所有核,這隻有在某一時刻只 允許一個應用程式才有意義。你可以通過spark.cores.maxSparkConf中設定核數。

val conf = new SparkConf()
             .setMaster(...)
             .setAppName(...)
             .set("spark.cores.max", "10")
val sc = new SparkContext(conf)

另外,你可以在叢集的master程式中配置spark.deploy.defaultCores來改變預設的值。在conf/spark-env.sh新增下面的行:

export SPARK_MASTER_OPTS="-Dspark.deploy.defaultCores=<value>"

這在使用者沒有配置最大核數的共享叢集中是有用的。


高可用

預設情況下,獨立的排程叢集對worker失敗是有彈性的(在Spark本身的範圍內是有彈性的,對丟失的工作通過轉移它到另外的worker來解決)。然而,排程器通過master去執行排程決定, 這會造成單點故障:如果master死了,新的應用程式就無法建立。為了避免這個,我們有兩個高可用的模式。

用ZooKeeper的備用master

利用ZooKeeper去支援領導選舉以及一些狀態儲存,你能夠在你的叢集中啟動多個master,這些master連線到同一個ZooKeeper例項上。一個被選為“領導”,其它的保持備用模式。如果當前 的領導死了,另一個master將會被選中,恢復老master的狀態,然後恢復排程。整個的恢復過程大概需要1到2分鐘。注意,這個恢復時間僅僅會影響排程新的應用程式-執行在失敗master中的 應用程式不受影響。

配置

為了開啟這個恢復模式,你可以用下面的屬性在spark-env中設定SPARK_DAEMON_JAVA_OPTS

System property Meaning
spark.deploy.recoveryMode 設定ZOOKEEPER去啟動備用master模式(預設為none)
spark.deploy.zookeeper.url zookeeper叢集url(如192.168.1.100:2181,192.168.1.101:2181)
spark.deploy.zookeeper.dir zookeeper儲存恢復狀態的目錄(預設是/spark)

可能的陷阱:如果你在叢集中有多個masters,但是沒有用zookeeper正確的配置這些masters,這些masters不會發現彼此,會認為它們都是leaders。這將會造成一個不健康的叢集狀態(因為所有的master都會獨立的排程)。

細節

zookeeper叢集啟動之後,開啟高可用是簡單的。在相同的zookeeper配置(zookeeper URL和目錄)下,在不同的節點上簡單地啟動多個master程式。master可以隨時新增和刪除。

為了排程新的應用程式或者新增worker到叢集,它需要知道當前leader的IP地址。這可以通過簡單的傳遞一個master列表來完成。例如,你可能啟動你的SparkContext指向spark://host1:port1,host2:port2。 這將造成你的SparkContext同時註冊這兩個master-如果host1死了,這個配置檔案將一直是正確的,因為我們將找到新的leader-host2

"registering with a Master"和正常操作之間有重要的區別。當啟動時,一個應用程式或者worker需要能夠發現和註冊當前的leader master。一旦它成功註冊,它就在系統中了。如果 錯誤發生,新的leader將會接觸所有之前註冊的應用程式和worker,通知他們領導關係的變化,所以它們甚至不需要事先知道新啟動的leader的存在。

由於這個屬性的存在,新的master可以在任何時候建立。你唯一需要擔心的問題是新的應用程式和workers能夠發現它並將它註冊進來以防它成為leader master。


用本地檔案系統做單節點恢復

zookeeper是生產環境下最好的選擇,但是如果你想在master死掉後重啟它,FILESYSTEM模式可以解決。當應用程式和worker註冊,它們擁有足夠的狀態寫入提供的目錄,以至於在重啟master 程式時它們能夠恢復。

配置

為了開啟這個恢復模式,你可以用下面的屬性在spark-env中設定SPARK_DAEMON_JAVA_OPTS

System property Meaning
spark.deploy.recoveryMode 設定為FILESYSTEM開啟單節點恢復模式(預設為none)
spark.deploy.recoveryDirectory 用來恢復狀態的目錄

細節

  • 這個解決方案可以和監控器/管理器(如monit)相配合,或者僅僅通過重啟開啟手動恢復。
  • 雖然檔案系統的恢復似乎比沒有做任何恢復要好,但對於特定的開發或實驗目的,這種模式可能是次優的。特別是,通過stop-master.sh殺掉master不會清除它的恢復狀態,所以,不管你何時啟動一個新的master,它都將進入恢復模式。這可能使啟動時間增加到1分鐘。
  • 雖然它不是官方支援的方式,你也可以建立一個NFS目錄作為恢復目錄。如果原始的master節點完全死掉,你可以在不同的節點啟動master,它可以正確的恢復之前註冊的所有應用程式和workers。未來的應用程式會發現這個新的master。


在YARN上執行Spark

配置

大部分為Spark on YARN模式提供的配置與其它部署模式提供的配置相同。下面這些是為Spark on YARN模式提供的配置。

Hadoop 配置

記得到yarn-site-xml下新增:

vim  $HADOOP_HOME/etc/hadoop/yarn-site.xml
<property>
    <name>yarn.nodemanager.pmem-check-enabled</name>
    <value>false</value>
</property>

<property>
    <name>yarn.nodemanager.vmem-check-enabled</name>
    <value>false</value>
</property> 

Spark屬性

Property Name Default Meaning
spark.yarn.applicationMaster.waitTries 10 ApplicationMaster等待Spark master的次數以及SparkContext初始化嘗試的次數
spark.yarn.submit.file.replication HDFS預設的複製次數(3) 上傳到HDFS的檔案的HDFS複製水平。這些檔案包括Spark jar、app jar以及任何分散式快取檔案/檔案
spark.yarn.preserve.staging.files false 設定為true,則在作業結束時保留階段性檔案(Spark jar、app jar以及任何分散式快取檔案)而不是刪除它們
spark.yarn.scheduler.heartbeat.interval-ms 5000 Spark application master給YARN ResourceManager傳送心跳的時間間隔(ms)
spark.yarn.max.executor.failures numExecutors * 2,最小為3 失敗應用程式之前最大的執行失敗數
spark.yarn.historyServer.address (none) Spark歷史伺服器(如host.com:18080)的地址。這個地址不應該包含一個模式(http://)。預設情況下沒有設定值,這是因為該選項是一個可選選項。當Spark應用程式完成從ResourceManager.xn--%2C-0n6a5a4i4cx5nm9cq9fs6huv1aztnca24z1v3a0o8d42yb1ma0wy41dt1aeaa8870cga5681b.xn--sparkresourcemanager-0w75an08pwqoodr0t8owxt5j2u2a/) UI到Spark歷史伺服器UI的連線時,這個地址從YARN ResourceManager得到
spark.yarn.dist.archives (none) 提取逗號分隔的檔案列表到每個執行器的工作目錄
spark.yarn.dist.files (none) 放置逗號分隔的檔案列表到每個執行器的工作目錄
spark.yarn.executor.memoryOverhead executorMemory * 0.07,最小384 分配給每個執行器的堆記憶體大小(以MB為單位)。它是VM開銷、interned字串或者其它本地開銷佔用的記憶體。這往往隨著執行器大小而增長。(典型情況下是6%-10%)
spark.yarn.driver.memoryOverhead driverMemory * 0.07,最小384 分配給每個driver的堆記憶體大小(以MB為單位)。它是VM開銷、interned字串或者其它本地開銷佔用的記憶體。這往往隨著執行器大小而增長。(典型情況下是6%-10%)
spark.yarn.queue default 應用程式被提交到的YARN佇列的名稱
spark.yarn.jar (none) Spark jar檔案的位置,覆蓋預設的位置。預設情況下,Spark on YARN將會用到本地安裝的Spark jar。但是Spark jar也可以HDFS中的一個公共位置。這允許YARN快取它到節點上,而不用在每次執行應用程式時都需要分配。指向HDFS中的jar包,可以這個引數為"hdfs:///some/path"
spark.yarn.access.namenodes (none) 你的Spark應用程式訪問的HDFS namenode列表。例如,spark.yarn.access.namenodes=hdfs://nn1.com:8032,hdfs://nn2.com:8032,Spark應用程式必須訪問namenode列表,Kerberos必須正確配置來訪問它們。Spark獲得namenode的安全令牌,這樣Spark應用程式就能夠訪問這些遠端的HDFS叢集。
spark.yarn.containerLauncherMaxThreads 25 為了啟動執行者容器,應用程式master用到的最大執行緒數
spark.yarn.appMasterEnv.[EnvironmentVariableName] (none) 新增通過EnvironmentVariableName指定的環境變數到Application Master處理YARN上的啟動。使用者可以指定多個該設定,從而設定多個環境變數。在yarn-cluster模式下,這控制Spark driver的環境。在yarn-client模式下,這僅僅控制執行器啟動者的環境。

在YARN上啟動Spark

確保HADOOP_CONF_DIRYARN_CONF_DIR指向的目錄包含Hadoop叢集的(客戶端)配置檔案。這些配置用於寫資料到dfs和連線到YARN ResourceManager。

有兩種部署模式可以用來在YARN上啟動Spark應用程式。在yarn-cluster模式下,Spark driver執行在application master程式中,這個程式被叢集中的YARN所管理,客戶端會在初始化應用程式 之後關閉。在yarn-client模式下,driver執行在客戶端程式中,application master僅僅用來向YARN請求資源。

和Spark單獨模式以及Mesos模式不同,在這些模式中,master的地址由"master"引數指定,而在YARN模式下,ResourceManager的地址從Hadoop配置得到。因此master引數是簡單的yarn-clientyarn-cluster

在yarn-cluster模式下啟動Spark應用程式。

./bin/spark-submit --class path.to.your.Class --master yarn-cluster [options] <app jar=""> [app options]

例子:

$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn-cluster \
    --num-executors 3 \
    --driver-memory 4g \
    --executor-memory 2g \
    --executor-cores 1 \
    --queue thequeue \
    lib/spark-examples*.jar \
    10

以上啟動了一個YARN客戶端程式用來啟動預設的 Application Master,然後SparkPi會作為Application Master的子執行緒執行。客戶端會定期的輪詢Application Master用於狀態更新並將 更新顯示在控制檯上。一旦你的應用程式執行完畢,客戶端就會退出。

在yarn-client模式下啟動Spark應用程式,執行下面的shell指令碼

$ ./bin/spark-shell --master yarn-client

新增其它的jar

在yarn-cluster模式下,driver執行在不同的機器上,所以離開了儲存在本地客戶端的檔案,SparkContext.addJar將不會工作。為了使SparkContext.addJar用到儲存在客戶端的檔案, 在啟動命令中加上--jars選項。

$ ./bin/spark-submit --class my.main.Class \
    --master yarn-cluster \
    --jars my-other-jar.jar,my-other-other-jar.jar
    my-main-jar.jar
    app_arg1 app_arg2

注意事項

  • 在Hadoop 2.2之前,YARN不支援容器核的資源請求。因此,當執行早期的版本時,通過命令列引數指定的核的數量無法傳遞給YARN。在排程決策中,核請求是否兌現取決於用哪個排程器以及 如何配置排程器。
  • Spark executors使用的本地目錄將會是YARN配置(yarn.nodemanager.local-dirs)的本地目錄。如果使用者指定了spark.local.dir,它將被忽略。
  • --files--archives選項支援指定帶 # 號檔名。例如,你能夠指定--files localtest.txt#appSees.txt,它上傳你在本地命名為localtest.txt的檔案到HDFS,但是將會連結為名稱appSees.txt。當你的應用程式執行在YARN上時,你應該使用appSees.txt去引用該檔案。
  • 如果你在yarn-cluster模式下執行SparkContext.addJar,並且用到了本地檔案, --jars選項允許SparkContext.addJar函式能夠工作。如果你正在使用 HDFS, HTTP, HTTPS或FTP,你不需要用到該選項


Spark配置

Spark提供三個位置用來配置系統:

  • Spark properties控制大部分的應用程式引數,可以用SparkConf物件或者java系統屬性設定
  • Environment variables可以通過每個節點的conf/spark-env.sh指令碼設定每臺機器的設定。例如IP地址
  • Logging可以通過log4j.properties配置

Spark屬性

Spark屬性控制大部分的應用程式設定,並且為每個應用程式分別配置它。這些屬性可以直接在SparkConf上配置,然後傳遞給SparkContextSparkConf 允許你配置一些通用的屬性(如master URL、應用程式明)以及通過set()方法設定的任意鍵值對。例如,我們可以用如下方式建立一個擁有兩個執行緒的應用程式。注意,我們用local[2]執行,這意味著兩個執行緒-表示最小的 並行度,它可以幫助我們檢測當在分散式環境下執行的時才出現的錯誤。

val conf = new SparkConf()
             .setMaster("local[2]")
             .setAppName("CountingSheep")
             .set("spark.executor.memory", "1g")
val sc = new SparkContext(conf)

注意,我們在本地模式中擁有超過1個執行緒。和Spark Streaming的情況一樣,我們可能需要一個執行緒防止任何形式的飢餓問題。

動態載入Spark屬性

在一些情況下,你可能想在SparkConf中避免硬編碼確定的配置。例如,你想用不同的master或者不同的記憶體數執行相同的應用程式。Spark允許你簡單地建立一個空conf。

val sc = new SparkContext(new SparkConf())

然後你在執行時提供值。

./bin/spark-submit --name "My app" --master local[4] --conf spark.shuffle.spill=false
  --conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps" myApp.jar

Spark shell和spark-submit工具支援兩種方式動態載入配置。第一種方式是命令列選項,例如--master,如上面shell顯示的那樣。spark-submit可以接受任何Spark屬性,用--conf 標記表示。但是那些參與Spark應用程式啟動的屬性要用特定的標記表示。執行./bin/spark-submit --help將會顯示選項的整個列表。

bin/spark-submit也會從conf/spark-defaults.conf中讀取配置選項,這個配置檔案中,每一行都包含一對以空格分開的鍵和值。例如:

spark.master            spark://5.6.7.8:7077
spark.executor.memory   512m
spark.eventLog.enabled  true
spark.serializer        org.apache.spark.serializer.KryoSerializer

任何標籤(flags)指定的值或者在配置檔案中的值將會傳遞給應用程式,並且通過SparkConf合併這些值。在SparkConf上設定的屬性具有最高的優先順序,其次是傳遞給spark-submit 或者spark-shell的屬性值,最後是spark-defaults.conf檔案中的屬性值。

檢視Spark屬性

http://<driver>:4040上的應用程式web UI在“Environment”標籤中列出了所有的Spark屬性。這對你確保設定的屬性的正確性是很有用的。注意,只有通過spark-defaults.conf, SparkConf以及 命令列直接指定的值才會顯示。對於其它的配置屬性,你可以認為程式用到了預設的值。

可用的屬性

控制內部設定的大部分屬性都有合理的預設值,一些最通用的選項設定如下:

應用程式屬性

Property Name Default Meaning
spark.app.name (none) 你的應用程式的名字。這將在UI和日誌資料中出現
spark.master (none) 叢集管理器連線的地方
spark.executor.memory 512m 每個executor程式使用的記憶體數。和JVM記憶體串擁有相同的格式(如512m,2g)
spark.driver.memory 512m driver程式使用的記憶體數
spark.driver.maxResultSize 1g 每個Spark action(如collect)所有分割槽的序列化結果的總大小限制。設定的值應該不小於1m,0代表沒有限制。如果總大小超過這個限制,工作將會終止。大的限制值可能導致driver出現記憶體溢位錯誤(依賴於spark.driver.memory和JVM中物件的記憶體消耗)。設定合理的限制,可以避免出現記憶體溢位錯誤。
spark.serializer org.apache.spark.serializer.JavaSerializer 序列化物件使用的類。預設的java序列化類可以序列化任何可序列化的java物件但是它很慢。所有我們建議用org.apache.spark.serializer.KryoSerializer
spark.kryo.classesToRegister (none) 如果你用Kryo序列化,給定的用逗號分隔的自定義類名列表表示要註冊的類
spark.kryo.registrator (none) 如果你用Kryo序列化,設定這個類去註冊你的自定義類。如果你需要用自定義的方式註冊你的類,那麼這個屬性是有用的。否則spark.kryo.classesToRegister會更簡單。它應該設定一個繼承自KryoRegistrator的類
spark.local.dir /tmp Spark中暫存空間的使用目錄。在Spark1.0以及更高的版本中,這個屬性被SPARK_LOCAL_DIRS(Standalone, Mesos)和LOCAL_DIRS(YARN)環境變數覆蓋。
spark.logConf false 當SparkContext啟動時,將有效的SparkConf記錄為INFO。

執行環境

Property Name Default Meaning
spark.executor.extraJavaOptions (none) 傳遞給executors的JVM選項字串。例如GC設定或者其它日誌設定。注意,在這個選項中設定Spark屬性或者堆大小是不合法的。Spark屬性需要用SparkConf物件或者spark-submit指令碼用到的spark-defaults.conf檔案設定。堆記憶體可以通過spark.executor.memory設定
spark.executor.extraClassPath (none) 附加到executors的classpath的額外的classpath實體。這個設定存在的主要目的是Spark與舊版本的向後相容問題。使用者一般不用設定這個選項
spark.executor.extraLibraryPath (none) 指定啟動executor的JVM時用到的庫路徑
spark.executor.logs.rolling.strategy (none) 設定executor日誌的滾動(rolling)策略。預設情況下沒有開啟。可以配置為time(基於時間的滾動)和size(基於大小的滾動)。對於time,用spark.executor.logs.rolling.time.interval設定滾動間隔;對於size,用spark.executor.logs.rolling.size.maxBytes設定最大的滾動大小
spark.executor.logs.rolling.time.interval daily executor日誌滾動的時間間隔。預設情況下沒有開啟。合法的值是daily, hourly, minutely以及任意的秒。
spark.executor.logs.rolling.size.maxBytes (none) executor日誌的最大滾動大小。預設情況下沒有開啟。值設定為位元組
spark.executor.logs.rolling.maxRetainedFiles (none) 設定被系統保留的最近滾動日誌檔案的數量。更老的日誌檔案將被刪除。預設沒有開啟。
spark.files.userClassPathFirst false (實驗性)當在Executors中載入類時,是否使用者新增的jar比Spark自己的jar優先順序高。這個屬性可以降低Spark依賴和使用者依賴的衝突。它現在還是一個實驗性的特徵。
spark.python.worker.memory 512m 在聚合期間,每個python worker程式使用的記憶體數。在聚合期間,如果記憶體超過了這個限制,它將會將資料塞進磁碟中
spark.python.profile false 在Python worker中開啟profiling。通過sc.show_profiles()展示分析結果。或者在driver退出前展示分析結果。可以通過sc.dump_profiles(path)將結果dump到磁碟中。如果一些分析結果已經手動展示,那麼在driver退出前,它們再不會自動展示
spark.python.profile.dump (none) driver退出前儲存分析結果的dump檔案的目錄。每個RDD都會分別dump一個檔案。可以通過ptats.Stats()載入這些檔案。如果指定了這個屬性,分析結果不會自動展示
spark.python.worker.reuse true 是否重用python worker。如果是,它將使用固定數量的Python workers,而不需要為每個任務fork()一個Python程式。如果有一個非常大的廣播,這個設定將非常有用。因為,廣播不需要為每個任務從JVM到Python worker傳遞一次
spark.executorEnv.[EnvironmentVariableName] (none) 通過EnvironmentVariableName新增指定的環境變數到executor程式。使用者可以指定多個EnvironmentVariableName,設定多個環境變數
spark.mesos.executor.home driver side SPARK_HOME 設定安裝在Mesos的executor上的Spark的目錄。預設情況下,executors將使用driver的Spark本地(home)目錄,這個目錄對它們不可見。注意,如果沒有通過spark.executor.uri指定Spark的二進位制包,這個設定才起作用
spark.mesos.executor.memoryOverhead executor memory * 0.07, 最小384m 這個值是spark.executor.memory的補充。它用來計算mesos任務的總記憶體。另外,有一個7%的硬編碼設定。最後的值將選擇spark.mesos.executor.memoryOverhead或者spark.executor.memory的7%二者之間的大者

Shuffle行為(Behavior)

Property Name Default Meaning
spark.shuffle.consolidateFiles false 如果設定為"true",在shuffle期間,合併的中間檔案將會被建立。建立更少的檔案可以提供檔案系統的shuffle的效率。這些shuffle都伴隨著大量遞迴任務。當用ext4和dfs檔案系統時,推薦設定為"true"。在ext3中,因為檔案系統的限制,這個選項可能機器(大於8核)降低效率
spark.shuffle.spill true 如果設定為"true",通過將多出的資料寫入磁碟來限制記憶體數。通過spark.shuffle.memoryFraction來指定spilling的閾值
spark.shuffle.spill.compress true 在shuffle時,是否將spilling的資料壓縮。壓縮演算法通過spark.io.compression.codec指定。
spark.shuffle.memoryFraction 0.2 如果spark.shuffle.spill為“true”,shuffle中聚合和合並組操作使用的java堆記憶體佔總記憶體的比重。在任何時候,shuffles使用的所有記憶體內maps的集合大小都受這個限制的約束。超過這個限制,spilling資料將會儲存到磁碟上。如果spilling太過頻繁,考慮增大這個值
spark.shuffle.compress true 是否壓縮map操作的輸出檔案。一般情況下,這是一個好的選擇。
spark.shuffle.file.buffer.kb 32 每個shuffle檔案輸出流記憶體內快取的大小,單位是kb。這個快取減少了建立只中間shuffle檔案中磁碟搜尋和系統訪問的數量
spark.reducer.maxMbInFlight 48 從遞迴任務中同時獲取的map輸出資料的最大大小(mb)。因為每一個輸出都需要我們建立一個快取用來接收,這個設定代表每個任務固定的記憶體上限,所以除非你有更大的記憶體,將其設定小一點
spark.shuffle.manager sort 它的實現用於shuffle資料。有兩種可用的實現:sorthash。基於sort的shuffle有更高的記憶體使用率
spark.shuffle.sort.bypassMergeThreshold 200 (Advanced) In the sort-based shuffle manager, avoid merge-sorting data if there is no map-side aggregation and there are at most this many reduce partitions
spark.shuffle.blockTransferService netty 實現用來在executor直接傳遞shuffle和快取塊。有兩種可用的實現:nettynio。基於netty的塊傳遞在具有相同的效率情況下更簡單

Spark UI

Property Name Default Meaning
spark.ui.port 4040 你的應用程式dashboard的埠。顯示記憶體和工作量資料
spark.ui.retainedStages 1000 在垃圾回收之前,Spark UI和狀態API記住的stage數
spark.ui.retainedJobs 1000 在垃圾回收之前,Spark UI和狀態API記住的job數
spark.ui.killEnabled true 執行在web UI中殺死stage和相應的job
spark.eventLog.enabled false 是否記錄Spark的事件日誌。這在應用程式完成後,重新構造web UI是有用的
spark.eventLog.compress false 是否壓縮事件日誌。需要spark.eventLog.enabled為true
spark.eventLog.dir file:///tmp/spark-events Spark事件日誌記錄的基本目錄。在這個基本目錄下,Spark為每個應用程式建立一個子目錄。各個應用程式記錄日誌到直到的目錄。使用者可能想設定這為統一的地點,像HDFS一樣,所以歷史檔案可以通過歷史伺服器讀取

壓縮和序列化

Property Name Default Meaning
spark.broadcast.compress true 在傳送廣播變數之前是否壓縮它
spark.rdd.compress true 是否壓縮序列化的RDD分割槽。在花費一些額外的CPU時間的同時節省大量的空間
spark.io.compression.codec snappy 壓縮諸如RDD分割槽、廣播變數、shuffle輸出等內部資料的編碼解碼器。預設情況下,Spark提供了三種選擇:lz4, lzf和snappy。你也可以用完整的類名來制定。org.apache.spark.io.LZ4CompressionCodecorg.apache.spark.io.LZFCompressionCodecorg.apache.spark.io.SnappyCompressionCodec
spark.io.compression.snappy.block.size 32768 Snappy壓縮中用到的塊大小。降低這個塊的大小也會降低shuffle記憶體使用率
spark.io.compression.lz4.block.size 32768 LZ4壓縮中用到的塊大小。降低這個塊的大小也會降低shuffle記憶體使用率
spark.closure.serializer org.apache.spark.serializer.JavaSerializer 閉包用到的序列化類。目前只支援java序列化器
spark.serializer.objectStreamReset 100 當用org.apache.spark.serializer.JavaSerializer序列化時,序列化器通過快取物件防止寫多餘的資料,然而這會造成這些物件的垃圾回收停止。通過請求'reset',你從序列化器中flush這些資訊並允許收集老的資料。為了關閉這個週期性的reset,你可以將值設為-1。預設情況下,每一百個物件reset一次
spark.kryo.referenceTracking true 當用Kryo序列化時,跟蹤是否引用同一物件。如果你的物件圖有環,這是必須的設定。如果他們包含相同物件的多個副本,這個設定對效率是有用的。如果你知道不在這兩個場景,那麼可以禁用它以提高效率
spark.kryo.registrationRequired false 是否需要註冊為Kyro可用。如果設定為true,然後如果一個沒有註冊的類序列化,Kyro會丟擲異常。如果設定為false,Kryo將會同時寫每個物件和其非註冊類名。寫類名可能造成顯著地效能瓶頸。
spark.kryoserializer.buffer.mb 0.064 Kyro序列化快取的大小。這樣worker上的每個核都有一個快取。如果有需要,快取會漲到spark.kryoserializer.buffer.max.mb設定的值那麼大。
spark.kryoserializer.buffer.max.mb 64 Kryo序列化快取允許的最大值。這個值必須大於你嘗試序列化的物件

Networking

Property Name Default Meaning
spark.driver.host (local hostname) driver監聽的主機名或者IP地址。這用於和executors以及獨立的master通訊
spark.driver.port (random) driver監聽的介面。這用於和executors以及獨立的master通訊
spark.fileserver.port (random) driver的檔案伺服器監聽的埠
spark.broadcast.port (random) driver的HTTP廣播伺服器監聽的埠
spark.replClassServer.port (random) driver的HTTP類伺服器監聽的埠
spark.blockManager.port (random) 塊管理器監聽的埠。這些同時存在於driver和executors
spark.executor.port (random) executor監聽的埠。用於與driver通訊
spark.port.maxRetries 16 當繫結到一個埠,在放棄前重試的最大次數
spark.akka.frameSize 10 在"control plane"通訊中允許的最大訊息大小。如果你的任務需要傳送大的結果到driver中,調大這個值
spark.akka.threads 4 通訊的actor執行緒數。當driver有很多CPU核時,調大它是有用的
spark.akka.timeout 100 Spark節點之間的通訊超時。單位是s
spark.akka.heartbeat.pauses 6000 This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). Acceptable heart beat pause in seconds for akka. This can be used to control sensitivity to gc pauses. Tune this in combination of spark.akka.heartbeat.interval and spark.akka.failure-detector.threshold if you need to.
spark.akka.failure-detector.threshold 300.0 This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). This maps to akka's akka.remote.transport-failure-detector.threshold. Tune this in combination of spark.akka.heartbeat.pauses and spark.akka.heartbeat.interval if you need to.
spark.akka.heartbeat.interval 1000 This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). A larger interval value in seconds reduces network overhead and a smaller value ( ~ 1 s) might be more informative for akka's failure detector. Tune this in combination of spark.akka.heartbeat.pauses and spark.akka.failure-detector.threshold if you need to. Only positive use case for using failure detector can be, a sensistive failure detector can help evict rogue executors really quick. However this is usually not the case as gc pauses and network lags are expected in a real Spark cluster. Apart from that enabling this leads to a lot of exchanges of heart beats between nodes leading to flooding the network with those.

Security

Property Name Default Meaning
spark.authenticate false 是否Spark驗證其內部連線。如果不是執行在YARN上,請看spark.authenticate.secret
spark.authenticate.secret None 設定Spark兩個元件之間的密匙驗證。如果不是執行在YARN上,但是需要驗證,這個選項必須設定
spark.core.connection.auth.wait.timeout 30 連線時等待驗證的實際。單位為秒
spark.core.connection.ack.wait.timeout 60 連線等待回答的時間。單位為秒。為了避免不希望的超時,你可以設定更大的值
spark.ui.filters None 應用到Spark web UI的用於過濾類名的逗號分隔的列表。過濾器必須是標準的javax servlet Filter。通過設定java系統屬性也可以指定每個過濾器的引數。spark.<class name="" of="" filter="">.params='param1=value1,param2=value2'。例如-Dspark.ui.filters=com.test.filter1-Dspark.com.test.filter1.params='param1=foo,param2=testing'
spark.acls.enable false 是否開啟Spark acls。如果開啟了,它檢查使用者是否有許可權去檢視或修改job。 Note this requires the user to be known, so if the user comes across as null no checks are done。UI利用使用過濾器驗證和設定使用者
spark.ui.view.acls empty 逗號分隔的使用者列表,列表中的使用者有檢視(view)Spark web UI的許可權。預設情況下,只有啟動Spark job的使用者有檢視許可權
spark.modify.acls empty 逗號分隔的使用者列表,列表中的使用者有修改Spark job的許可權。預設情況下,只有啟動Spark job的使用者有修改許可權
spark.admin.acls empty 逗號分隔的使用者或者管理員列表,列表中的使用者或管理員有檢視和修改所有Spark job的許可權。如果你執行在一個共享叢集,有一組管理員或開發者幫助debug,這個選項有用

Spark Streaming

Property Name Default Meaning
spark.streaming.blockInterval 200 在這個時間間隔(ms)內,通過Spark Streaming receivers接收的資料在儲存到Spark之前,chunk為資料塊。推薦的最小值為50ms
spark.streaming.receiver.maxRate infinite 每秒鐘每個receiver將接收的資料的最大記錄數。有效的情況下,每個流將消耗至少這個數目的記錄。設定這個配置為0或者-1將會不作限制
spark.streaming.receiver.writeAheadLogs.enable false Enable write ahead logs for receivers. All the input data received through receivers will be saved to write ahead logs that will allow it to be recovered after driver failures
spark.streaming.unpersist true 強制通過Spark Streaming生成並持久化的RDD自動從Spark記憶體中非持久化。通過Spark Streaming接收的原始輸入資料也將清除。設定這個屬性為false允許流應用程式訪問原始資料和持久化RDD,因為它們沒有被自動清除。但是它會造成更高的記憶體花費

環境變數

通過環境變數配置確定的Spark設定。環境變數從Spark安裝目錄下的conf/spark-env.sh指令碼讀取(或者windows的conf/spark-env.cmd)。在獨立的或者Mesos模式下,這個檔案可以給機器 確定的資訊,如主機名。當執行本地應用程式或者提交指令碼時,它也起作用。

注意,當Spark安裝時,conf/spark-env.sh預設是不存在的。你可以複製conf/spark-env.sh.template建立它。

可以在spark-env.sh中設定如下變數:

Environment Variable Meaning
JAVA_HOME java安裝的路徑
PYSPARK_PYTHON PySpark用到的Python二進位制執行檔案路徑
SPARK_LOCAL_IP 機器繫結的IP地址
SPARK_PUBLIC_DNS 你Spark應用程式通知給其他機器的主機名

除了以上這些,Spark standalone cluster scripts也可以設定一些選項。例如 每臺機器使用的核數以及最大記憶體。

因為spark-env.sh是shell指令碼,其中的一些可以以程式設計方式設定。例如,你可以通過特定的網路介面計算SPARK_LOCAL_IP

配置Logging

Spark用log4j logging。你可以通過在conf目錄下新增log4j.properties檔案來配置。一種方法是複製log4j.properties.template檔案。



Spark調優

由於大部分Spark計算都是在記憶體中完成的,所以Spark程式的瓶頸可能由叢集中任意一種資源導致,如:CPU、網路頻寬、或者記憶體等。最常見的情況是,資料能裝進記憶體,而瓶頸是網路頻寬;當然,有時候我們也需要做一些優化調整來減少記憶體佔用,例如將RDD以序列化格式儲存。 本文將主要涵蓋兩個主題:1.資料序列化(這對於優化網路效能極為重要);2.減少記憶體佔用以及記憶體調優。同時,我們也會提及其他幾個比較小的主題。

資料序列化

序列化在任何一種分散式應用效能優化時都扮演幾位重要的角色。如果序列化格式序列化過程緩慢,或者需要佔用位元組很多,都會大大拖慢整體的計算效率。 通常,序列化都是Spark應用優化時首先需要關注的地方。Spark著眼於便利性(允許你在計算過程中使用任何Java型別)和效能的一個平衡。Spark主要提供了兩個序列化庫:

  • Java serialization:預設情況,Spark使用Java自帶的ObjectOutputStream框架來序列化物件,這樣任何實現了 java.io.Serializable 介面的物件,都能被序列化。同時,你還可以通過擴充套件java.io.Externalizable 來控制序列化效能。Java序列化很靈活但效能較差,同時序列化後佔用的位元組數也較多。
  • Kryo serialization: Spark還可以使用Kryo庫(版本2)提供更高效的序列化格式。Kryo的序列化速度和位元組佔用都比Java序列化好很多(通常是10倍左右),但Kryo不支援所有實現了Serializable 介面的型別,它需要你在程式中register 需要序列化的型別,以得到最佳效能。

要切換使用 Kryo,你可以在 SparkConf 初始化的時候呼叫 conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)。這個設定不僅控制各個worker節點之間的混洗資料序列化格式,同時還控制RDD存到磁碟上的序列化格式。 目前,Kryo不是預設的序列化格式,因為它需要你在使用前註冊需要序列化的型別,不過我們還是建議在對網路敏感的應用場景下使用Kryo

Spark對一些常用的Scala核心型別,如在Twitter chill 庫的AllScalaRegistrar中,自動使用Kryo序列化格式。

如果你的自定義型別需要使用Kryo序列化,可以用 registerKryoClasses 方法先註冊:

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

Kryo的文件中有詳細描述了更多的高階選項,如:自定義序列化程式碼等。

如果你的物件很大,你可能需要增大 spark.kryoserializer.buffer 配置項。其值至少需要大於最大物件的序列化長度。

最後,如果你不註冊需要序列化的自定義型別,Kryo也能工作,不過每一個物件例項的序列化結果都會包含一份完整的類名,這有點浪費空間。


記憶體調優

記憶體佔用調優主要需要考慮3點:資料佔用的總記憶體(你會希望整個資料集都能裝進記憶體);訪問資料集中每個物件的開銷;垃圾回收的開銷(如果你的資料集中物件週轉速度很快的話)。

一般情況下,Java物件的訪問時很快的,但同時Java物件會比原始資料(僅包含各個欄位值)佔用的空間多2~5倍。主要原因有:

  • 每個Java物件都有一個物件頭(object header),物件頭大約佔用16位元組,其中包含像其對應class的指標這樣的資訊。對於一些包含較少資料的物件(比如只包含一個Int欄位),這個物件頭可能比物件資料本身還大。
  • Java字串(String)有大約40位元組額外開銷(Java StringChar資料的形式儲存原始資料,所以需要一些額外的欄位,如陣列長度等),並且每個字元都以兩位元組的UTF-16編碼在內部儲存。因此,10個字元的String很容易就佔了60位元組。
  • 一些常見的集合類,如 HashMapLinkedList,使用的是連結串列類資料結構,因此它們對每項資料都有一個包裝器。這些包裝器物件不僅其自身就有“物件頭”,同時還有指向下一個包裝器物件的連結串列指標(通常為8位元組)。
  • 原始型別的集合通常也是以“裝箱”的形式包裝成物件(如:java.lang.Integer)。

本節只是Spark記憶體管理的一個概要,下面我們會更詳細地討論各種Spark記憶體調優的具體策略。特別地,我們會討論如何評估資料的記憶體使用量,以及如何改進 – 要麼改變你的資料結構,要麼以某種序列化格式儲存資料。 最後,我們還會討論如何調整Spark的快取大小,以及如何調優Java的垃圾回收器。

記憶體管理概覽

Spark中記憶體主要用於兩類目的:執行計算和資料儲存。執行計算的記憶體主要用於Shuffle、關聯(join)、排序(sort)以及聚合(aggregation),而資料儲存的記憶體主要用於快取和叢集內部資料傳播。Spark中執行計算和資料儲存都是共享同一個記憶體區域(M)。 如果執行計算沒有佔用記憶體,那麼資料儲存可以申請佔用所有可用的記憶體,反之亦然。執行計算可能會搶佔資料儲存使用的記憶體,並將儲存於記憶體的資料逐出記憶體,直到資料儲存佔用的記憶體比例降低到一個指定的比例(R)。 換句話說,RM基礎上的一個子區域,這個區域的記憶體資料永遠不會被逐出記憶體。然而,資料儲存不會搶佔執行計算的記憶體。

這樣設計主要有這麼幾個需要考慮的點。首先,不需要快取資料的應用可以把整個空間用來執行計算,從而避免頻繁地把資料吐到磁碟上。其次,需要快取資料的應用能夠有一個資料儲存比例(R)的最低保證,也避免這部分快取資料被全部逐出記憶體。最後,這個實現方式能夠在預設情況下,為大多數使用場景提供合理的效能,而不需要專家級使用者來設定記憶體使用如何劃分。

雖然有兩個記憶體劃分相關的配置引數,但一般來說,使用者不需要設定,因為預設值已經能夠適用於絕大部分的使用場景:

  • spark.memory.fraction:表示上面M的大小,其值為相對於JVM堆記憶體的比例(預設0.75)。剩餘的25%是為其他使用者的資料結構、Spark內部後設資料以及避免OOM錯誤的安全預留空間。
  • spark.memory.storageFraction:表示上面R的大小,其值為相對於M的一個比例(預設0.5)。RM中專門用於快取資料塊的部分,這部分資料塊永遠不會因執行計算任務而逐出記憶體。

評估記憶體消耗

確定一個資料集佔用記憶體總量最好的辦法就是,建立一個RDD,並快取到記憶體中,然後再到web UI上”Storage”頁面檢視。頁面上會展示這個RDD總共佔用了多少記憶體。

要評估一個特定物件的記憶體佔用量,可以用 SizeEstimator.estimate 方法。這個方法對試驗哪種資料結構能夠裁剪記憶體佔用量比較有用,同時,也可以幫助使用者瞭解廣播變數在每個執行器堆上佔用的記憶體量。

資料結構調優

減少記憶體消耗的首要方法就是避免過多的Java封裝(減少物件頭和額外輔助欄位),比如基於指標的資料結構和包裝物件等。以下有幾條建議:

  • 設計資料結構的時候,優先使用物件陣列和原生型別,減少對複雜集合型別(如:HashMap)的使用。fastutil 提供了一些很方便的原生型別集合,同時相容Java標準庫。
  • 儘可能避免巢狀大量的小物件和指標。
  • 對應鍵值應儘量使用數值型或列舉型,而不是字串型。
  • 如果記憶體小於32GB,可以設定JVM標誌引數 -XX:+UseCompressdOops 將指標設為4位元組而不是8位元組。你可以在 spark-env.sh 中設定這個引數。

序列化RDD儲存

如果經過上面的調整後,儲存的資料物件還是太大,那麼你可以試試將這些物件以序列化格式儲存,所需要做的只是通過 RDD persistence API 設定好儲存級別,如:MEMORY_ONLY_SERSpark會將RDD的每個分割槽以一個巨大的位元組陣列形式儲存起來。以序列化格式儲存的唯一缺點就是訪問資料會變慢一點,因為Spark需要反序列化每個被訪問的物件。 如果你需要序列化快取資料,我們強烈建議你使用Kryo,和Java序列化相比,Kryo能大大減少序列化物件佔用的空間(當然也比原始Java物件小很多)。


垃圾回收調優

JVM的垃圾回收在某些情況下可能會造成瓶頸,比如,你的RDD儲存經常需要“換入換出”(新RDD搶佔了老RDD記憶體,不過如果你的程式沒有這種情況的話那JVM垃圾回收一般不是問題,比如,你的RDD只是載入一次,後續只是在這一個RDD上做操作)。當Java需要把老物件逐出記憶體的時候,JVM需要跟蹤所有的Java物件,並找出哪些物件已經沒有用了。 概括起來就是,垃圾回收的開銷和物件個數成正比,所以減少物件的個數(比如用Int陣列取代LinkedList),就能大大減少垃圾回收的開銷。 當然,一個更好的方法就如前面所說的,以序列化形式儲存資料,這時每個RDD分割槽都只包含有一個物件了(一個巨大的位元組陣列)。在嘗試其他技術方案前,首先可以試試用序列化RDD的方式(serialized caching)評估一下GC是不是一個瓶頸。

如果你的作業中各個任務需要的工作記憶體和節點上儲存的RDD快取佔用的記憶體產生衝突,那麼GC很可能會出現問題。下面我們將討論一下如何控制好RDD快取使用的記憶體空間,以減少這種衝突。

衡量GC的影響

GC調優的第一步是統計一下,垃圾回收啟動的頻率以及GC所使用的總時間。給JVM設定一下這幾個引數(參考Spark配置指南,檢視Spark作業中的Java選項引數):-verbose:gc -XX:+PrintGCDetails,就可以在後續Spark作業的worker日誌中看到每次GC花費的時間。 注意,這些日誌是在叢集worker節點上(在各節點的工作目錄下stdout檔案中),而不是你的驅動器所在節點。

高階GC調優

為了進一步調優GC,我們就需要對JVM記憶體管理有一個基本的瞭解:

  • Java堆記憶體可分配的空間有兩個區域:新生代(Young generation)和老年代(Old generation)。新生代用以儲存生存週期短的物件,而老年代則是儲存生存週期長的物件。
  • 新生代區域被進一步劃分為三個子區域:EdenSurvivor1Survivor2
  • 簡要描述一下垃圾回收的過程:如果Eden區滿了,則啟動一輪minor GC回收Eden中的物件,生存下來(沒有被回收掉)的Eden中的物件和Survivor1區中的物件一併複製到Survivor2中。 兩個Survivor區域是互相切換使用的(就是說,下次從EdenSurvivor2中複製到Survivor1中)。如果某個物件的年齡(每次GC所有生存下來的物件長一歲)超過某個閾值,或者Survivor2(下次是Survivor1)區域滿了,則將物件移到老年代(Old區)。最終如果老生代也滿了,就會啟動full GC

Spark GC調優的目標就是確保老年代(Old generation )只儲存長生命週期RDD,而同時新生代(Young generation)的空間又能足夠儲存短生命週期的物件。這樣就能在任務執行期間,避免啟動full GC。以下是GC調優的主要步驟:

  • GC的統計日誌中觀察GC是否啟動太多。如果某個任務結束前,多次啟動了full GC,則意味著用以執行該任務的記憶體不夠。
  • 如果GC統計資訊中顯示,老生代記憶體空間已經接近存滿,可以通過降低spark.memory.storageFraction 來減少RDD快取佔用的記憶體;減少快取物件總比任務執行緩慢要強!
  • 如果major GC比較少,但minor GC很多的話,可以多分配一些Eden記憶體。你可以把Eden的大小設為高於各個任務執行所需的工作記憶體。如果要把Eden大小設為E,則可以這樣設定新生代區域大小:-Xmn=4/3*E。(放大4/3倍,主要是為了給Survivor區域保留空間)
  • 舉例來說,如果你的任務會從HDFS上讀取資料,那麼單個任務的記憶體需求可以用其所讀取的HDFS資料塊的大小來評估。需要特別注意的是,解壓後的HDFS塊是解壓前的2~3倍。所以如果我們希望保留3~4個任務並行的工作記憶體,並且HDFS塊大小為64MB,那麼可以評估Eden的大小應該設為 4*3*64MB
  • 最後,再觀察一下垃圾回收的啟動頻率和總耗時有沒有什麼變化。

我們的很多經驗表明,GC調優的效果和你的程式程式碼以及可用的總記憶體相關。網上還有不少調優的選擇,但總體來說,就是控制好full GC的啟動頻率,就能有效減少垃圾回收開銷。


其他事項

並行度

一般來說叢集並不會滿負荷運轉,除非你把每個操作的並行度都設得足夠大。Spark會自動根據對應的輸入檔案大小來設定“map”類運算元的並行度(當然你可以通過一個SparkContext.textFile等函式的可選引數來控制並行度),而對於想 groupByKeyreduceByKey這類 “reduce” 運算元,會使用其各父RDD分割槽數的最大值。你可以將並行度作為構建RDD第二個引數(參考spark.PairRDDFunctions),或者設定 spark.default.parallelism 這個預設值。一般來說,評估並行度的時候,我們建議2~3個任務共享一個CPU

Reduce任務的記憶體佔用

如果RDD比記憶體要大,有時候你可能收到一個OutOfMemoryError錯誤,其實這是因為你的任務集中的某個任務太大了,如reduce任務groupByKeySparkShuffle運算元(sortByKey,groupByKey,reduceByKey,join等)會在每個任務中構建一個雜湊表,以便在任務中對資料分組,這個雜湊表有時會很大。最簡單的修復辦法就是增大並行度,以減小單個任務的輸入集。Spark對於200ms以內的短任務支援非常好,因為Spark可以跨任務複用執行器JVM,任務的啟動開銷很小,因此把並行度增加到比叢集中總CPU核數沒有任何問題。

廣播大變數

使用SparkContext中的廣播變數相關功能(broadcast functionality)能大大減少每個任務本身序列化的大小,以及叢集中啟動作業的開銷。如果你的Spark任務正在使用驅動程式中定義的巨大物件(比如:靜態查詢表),請考慮使用廣播變數替代。Spark會在master上將各個任務的序列化後大小列印出來,所以你可以檢查一下各個任務是否過大;通常來說,大於20KB的任務就值得優化一下。

資料本地性

資料本地性對Spark作業往往會有較大的影響。如果程式碼和其所操作的資料在同一節點上,那麼計算速度肯定會更快一些。但如果二者不在一起,那必然需要移動其中之一。一般來說,移動序列化好的程式碼肯定比挪動一大堆資料要快。Spark就是基於這個一般性原則來構建資料本地性的排程。

資料本地性是指程式碼和其所處理的資料的距離。基於資料當前的位置,資料本地性可以劃分成以下幾個層次(按從近到遠排序):

  • PROCESS_LOCAL 資料和執行的程式碼處於同一個JVM程式內。
  • NODE_LOCAL 資料和程式碼處於同一節點。例如,資料處於HDFS上某個節點,而對應的執行器(executor)也在同一個機器節點上。這會比PROCESS_LOCAL稍微慢一些,因為資料需要跨程式傳遞。
  • NO_PREF 資料在任何地方處理都一樣,沒有本地性偏好。
  • RACK_LOCAL 資料和程式碼處於同一個機架上的不同機器。這時,資料和程式碼處於不同機器上,需要通過網路傳遞,但還是在同一個機架上,一般也就通過一個交換機傳輸即可。
  • ANY 資料在網路中未知,即資料和程式碼不在同一個機架上。

Spark傾向於讓所有任務都具有最佳的資料本地性,但這並非總是可行的。某些情況下,可能會出現一些空閒的執行器(executor)沒有待處理的資料,那麼Spark可能就會犧牲一些資料本地性。有兩種可能的選項:a)等待已經有任務的CPU,待其釋放後立即在同一臺機器上啟動一個任務;b)立即在其他節點上啟動新任務,並把所需要的資料複製過去。

通常,Spark會等待一會,看看是否有CPU會被釋放出來。一旦等待超時,則立即在其他節點上啟動並將所需的資料複製過去。資料本地性各個級別之間的回落超時可以單獨配置,也可以在統一引數內一起設定;詳細請參考 configuration page 中的 spark.locality 相關引數。如果你的任務執行時間比較長並且資料本地性很差,你就應該試試調大這幾個引數,不過預設值一般都能適用於大多數場景了。

相關文章