一, 簡介
注:本文爲OsChina上原創文章,如需轉載請註明出處!還請見諒
Spark是一個用來實現快速而通用的集羣內存計算的平臺。擴展了廣泛使用的MapReduce計算模型,而且高效地支持更多的計算模式,包括交互式查詢和流處理。 隨着時間推移開始被大家逐漸熟知和普及,很長一段時間幾乎就是大數據代言詞。
二,前言
本文旨在幫助大家整理一些開發中常見的坑和誤區,幫助大家進行整理和總結,降低剛接觸的大數據開發朋友的開發難度.
對於算子和api大家在開發中參考官網文檔即可(http://spark.apache.org/docs/latest/)
注意:以下所講主要針對集羣生產環境
三, spark程序架構
Spark開發,嚴格來說屬於分佈式編程, spark程序工作時通常最少2個進程, 有且只有兩個角色driver
,executor
.
四, 常見流程(以yarn爲例)
- 1, 本地ide開發邏輯,並使用 local[*] 進行調試
- 2, 部署集羣使用spark-client進行測試(可能沒有)
- 3, 部署集羣使用spark-cluster實際部署
我們一般開發一個spark程序都要走上面的3個步驟,但是有些非常奇怪的現象是:步驟1沒問題但是實際到集羣部署,到了2和3就出錯.
常見錯誤和問題:
- 讀取的配置參數和預期不符合
- submit提交後出現
java.lang.NullPointerException
- Not Serializable
- 還有spark開發代碼中爲什麼經常看到lazy來獲取資源?
我想大部分朋友剛開始都被這些問題困擾過,如果你碰到了這些問題,我們不妨繼續往下看.
五, 分佈式編程
我們剛纔提到了Spark
程序的架構和角色,也指出spark開發其實是分佈式編程
.
分佈式編程?
通常大家最早接觸Spark開發,應該都是官網文檔demo helloworld,以及文檔開始的(我也是這麼上車的-_-!).
通過一些非常炫酷的(算子)方法調用.恩,反正最後helloworld就這麼跑出來了.於是上面提到的過程1就完成了.
第一印象: 這就是普通編程啊(這些方法算子scala就有,java8 Stream api也能搞出來),恩,這裏我們已經進入陷阱了(那麼上面提到部署坑你很可能即將碰到).
實際情況:
No!No!No! spark開發屬於分佈式編程, 我們書寫每一行code/每個函數/每個Class,都應該去思考代碼的實際位置,去規劃你的代碼結構.
舉個栗子:
object Test {
private val conn1 = crateteConn()
private lazy val conn2 = crateteConn();
def main() {
val conn3 = crateteConn() //這個code在driver角色進程上執行
val logo = “hello-spark”
rdd.foreachPartition(partition => {
val conn4 = crateteConn() //這個code在executor角色進程上執行
partition.foreach(line => {
println(logo)
println(conn1)
println(conn2)
println(conn3)
println(conn4)
})
})
}
}
栗子中指出了conn3和conn4創建時實際運行位置. 1和2留給大家自己思考 另外這個例子中代碼放在了 object(scala單例對象)中,如果放到class下面呢?
這就是爲什麼ide中沒問題,submit就出錯的大部分根源,抱歉: 這裏只爲讓大家意識到這個和平時編程的區別,以及幾個面向對象的概念,因篇幅有限沒辦法一一幫大家梳理.
但是spark進階之路上一定少不了關於分佈式編程的理解.在空閒情況不妨去交流和理解spark分佈式編程,者對於在flink,mr,beam等其他分佈式計算框架中都是通用的.
六, 執行機制
剛纔我們聊了程序模型實質是分佈式編程.接下來我們聊聊Spark提供的一些常見api機制.
- Spark編程,屬於標記(tranformtion)編程,由終結指令(action)觸發實際任務允許.
特點可以一個字總結,那就是
懶
.
如果你代碼中有下面的句子,那麼它一定是不執行的(注意:這些句子沒有下文):
rdd.map(x=>println(x))
spark.sparkContext.textFile("/ideal/hadoop/spark/README.md")
而下面這個一定會執行的(你可以在控制檯看到打印):
rdd.foreach(x=>println(x))
接下來我們看action算子
- 另外注意action算子之間是串行執行的,不過通常這完全符合我們的行爲預期 如:
println(rdd.count()) //必須執行玩才執行下面的語句
rdd.foreach(x=>println(x))
- 結合上面提到分佈式編程來個比喻:
spark開發就像一家公司大了後,老闆就只在紙上畫出接下來使用什麼資源(source)來幹什麼事情(tranformtion 標記).老闆規劃完成後,然後下指令(action)開幹,然後等待結果. 工程開始後driver理解這個圖紙,安排executor幹活.完成後driver最後向老闆彙報結果
七, 信息傳遞
既然是分佈式模型,那麼自然少不了信息溝通,這裏分爲信息收集到driver和信息下發到executor
1, 信息收集到driver
通常都是用spark自己提供的action算子
val cnt = rdd.count()
val data = rdd.collect()
count()計算,這個活是executor乾的,但是最後的結果cnt,是被收集到driver進程中的
請謹慎使用action collect(),不明所以的情況下dirver直接oom掉
如果確實需要用collect怎麼辦? 推薦使用下面的api
rdd.toLocalIterator
dataframe.toLocalIterator
這裏同樣不推薦rdd.repartition(1)
算子,容易造成executor 直接oom掉
2, 信息下發到executor
這種情況很常見幾乎每個程序都會用到,具體分爲啓動初始化時參數傳遞和允許中傳遞:
- 1, 利用閉包特性(高大上,駕馭有難度,各種跨進程rpc編程,分佈式編程框架所使用,缺點:jvm專用,一般在啓動初始化時傳遞)
- 2, 利用--files 傳遞配置文件, executor 通過lazy讀取
private lazy val conn: Connection = {
//1 LOAD 配置文件
val user = "read --files的配置文件"
DriverManager.getConnection("jdbc:",user,"passsword")
}
特點: 親民廣泛,內容運維可以配置,就像普通程序一樣帶配置文件部署, executor可以輕易獲取, scala和python,R都可以使用,屬於啓動初始化時傳遞信息
- 3, 利用spark的廣播功能,注意可序列化,屬於允許中傳遞
- 4, 利用spark-submit和yarn機制
set=>: spark.executorEnv.[EnvironmentVariableName]=value
jvm_read=>: val value = System.getEnv(EnvironmentVariableName)
特點: 如果參數少且簡單非常推薦, 完美髮揮lazy,且scala和python,R都可以使用,也屬於啓動初始化時傳遞信息
八, 總結
本文旨的告訴大家Spark開發本質是分佈式編程,Spark是一個分佈式編程框架(平臺,語言),我們常見的很多問題以及調優等都需要大家去注意這一點.同時幫大家梳理了一些基本的知識點,當然了spark等分佈式計算開發有非常多技術點,因篇幅問題在此無法一一列舉,還請見諒.
九, 最後
如果大家有興趣除了關注後續的文章,也隨時歡迎和我們討論:
您也可以關注下我的開源大數據流計算項目Sylph(https://www.oschina.net/p/sylph),裏面用到flink,spark等非常多的特性