spark 開發中的那些事1-之編程模型

一, 簡介

注:本文爲OsChina上原創文章,如需轉載請註明出處!還請見諒

Spark是一個用來實現快速而通用的集羣內存計算的平臺。擴展了廣泛使用的MapReduce計算模型,而且高效地支持更多的計算模式,包括交互式查詢和流處理。 隨着時間推移開始被大家逐漸熟知和普及,很長一段時間幾乎就是大數據代言詞。

二,前言

本文旨在幫助大家整理一些開發中常見的坑和誤區,幫助大家進行整理和總結,降低剛接觸的大數據開發朋友的開發難度.

對於算子和api大家在開發中參考官網文檔即可(http://spark.apache.org/docs/latest/)

注意:以下所講主要針對集羣生產環境

三, spark程序架構

loading...

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等非常多的特性

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