貫通Spark Streaming JobScheduler內(nèi)幕實(shí)現(xiàn)和深入思考

? ? ? ?JobScheduler的地位非常的重要,所有的關(guān)鍵都在JobScheduler,它的重要性就相當(dāng)于是Spark Core當(dāng)中的DAGScheduler,因此,我們要花重點(diǎn)在JobScheduler上面。

? ? ? ?我們?cè)谶M(jìn)行sparkstreaming開發(fā)的時(shí)候,會(huì)對(duì)Dstream進(jìn)行各種transform和action級(jí)別的操作,這些操作就構(gòu)成Dstream graph,也就是Dstream 之間的依賴關(guān)系,隨著時(shí)間的流逝,Dstream graph會(huì)根據(jù)batchintaval時(shí)間間隔,產(chǎn)生RDD的DAG,然后進(jìn)行job的執(zhí)行。Dstream 的Dstream graph是邏輯級(jí)別的,RDD的DAG是物理執(zhí)行級(jí)別的。DStream是空間維度的層面,空間維度加上時(shí)間構(gòu)成時(shí)空維度。

? ? ? ?JobSchedule是將邏輯級(jí)別的job物理的運(yùn)行在spark core上。JobGenerator是產(chǎn)生邏輯級(jí)別的job,使用JobSchedule將job在線程池中運(yùn)行。JobSchedule是在StreamingContext中進(jìn)行實(shí)例化的,并在StreamingContext的start方法中開辟一條新的線程啟動(dòng)的。

// Start the streaming scheduler in a new thread, so that thread local properties

// like call sites and job groups can be reset without affecting those of the

// current thread.ThreadUtils.runInNewThread("streaming-start") {

sparkContext.setCallSite(startSite.get)

sparkContext.clearJobGroup()

sparkContext.setLocalProperty(SparkContext.SPARK_JOB_INTERRUPT_ON_CANCEL, "false")

scheduler.start()

}

? ? ? 1.大括號(hào)中的代碼作為一個(gè)匿名函數(shù)在新的線程中執(zhí)行。Sparkstreaming運(yùn)行時(shí)至少需要兩條線程,其中一條用于一直循環(huán)接收數(shù)據(jù),現(xiàn)在所說的至少兩條線程和上邊開辟一條新線程運(yùn)行scheduler.start()并沒有關(guān)系。Sparkstreaming運(yùn)行時(shí)至少需要兩條線程是用于作業(yè)處理的,上邊的代碼開辟新的線程是在調(diào)度層面的中,不論Sparkstreaming程序運(yùn)行時(shí)指定多少線程,這里都會(huì)開辟一條新線程,之間沒有一點(diǎn)關(guān)系。

? ? ? 2.每一條線程都有自己私有的屬性,在這里給新的線程設(shè)置私有的屬性,這些屬性不會(huì)影響主線程中的。

sparkContext.setCallSite(startSite.get)

sparkContext.clearJobGroup()

sparkContext.setLocalProperty(SparkContext.SPARK_JOB_INTERRUPT_ON_CANCEL, "false")

? ? ? 源碼中代碼的書寫模式非常值得學(xué)習(xí),以后看源碼的時(shí)候就把它當(dāng)做是一個(gè)普通的應(yīng)用程序,從jvm的角度看,spark就是一個(gè)分布式的應(yīng)用程序。不要對(duì)源碼有代碼崇拜情節(jié),不然就沒有掌控源碼的信心。

JobSchedule在實(shí)例化的時(shí)候會(huì)實(shí)例化JobGenerator和線程池。

private valnumConcurrentJobs= ssc.conf.getInt("spark.streaming.concurrentJobs", 1)

private valjobExecutor=

ThreadUtils.newDaemonFixedThreadPool(numConcurrentJobs, "streaming-job-executor")

private valjobGenerator=newJobGenerator(this)

? ? ? 線程池中默認(rèn)是有一條線程,當(dāng)然可以在spark配置文件中配置或者使用代碼在sparkconf中修改默認(rèn)的線程數(shù),在一定程度上增加默認(rèn)線程數(shù)可以提高執(zhí)行job的效率,這也是一個(gè)性能調(diào)優(yōu)的方法(尤其是在一個(gè)程序中有多個(gè)job時(shí))。

? ? ? Java在企業(yè)生產(chǎn)環(huán)境下已經(jīng)形成了生態(tài)系統(tǒng),在spark開發(fā)中和數(shù)據(jù)庫、hbase、radis、javaEE交互一般都采用java,所以開發(fā)大型spark項(xiàng)目大部分都是scala+java的方式進(jìn)行開發(fā)。

? ? ? JobGenerator和線程池在JobSchedule在實(shí)例化的時(shí)候就已經(jīng)實(shí)例化了,而eventloop和receiverTracker是在調(diào)用JobGenerator的start方法時(shí)才被實(shí)例化。defstart(): Unit = synchronized {

if(eventLoop!=null)return// scheduler has already been started

eventLoop=newEventLoop[JobSchedulerEvent]("JobScheduler") {

override protected defonReceive(event: JobSchedulerEvent): Unit = processEvent(event)override protected defonError(e: Throwable): Unit = reportError("Error in job scheduler", e)

}

eventLoop.start()

receiverTracker=newReceiverTracker(ssc)

receiverTracker.start()

jobGenerator.start()

}

? ? ? ? 在eventloop的start方法中會(huì)回調(diào)onStart方法,一般在onStart方法中會(huì)執(zhí)行一些準(zhǔn)備性的代碼,在JobSchedule中雖然并沒有復(fù)寫onStart方法,不過sparkStreaming框架在這里顯然是為了代碼的可擴(kuò)展性考慮的,這是開發(fā)項(xiàng)目時(shí)需要學(xué)習(xí)的。

defstart(): Unit = {

if(stopped.get) {

throw newIllegalStateException(name + " has already been stopped")

}

// Call onStart before starting the event thread to make sure it happens before onReceive

onStart()

eventThread.start()

}

Dstream的action級(jí)別的操作轉(zhuǎn)過來還是會(huì)調(diào)用foreachRDD這個(gè)方法,生動(dòng)的說明在對(duì)Dstream操作的時(shí)候其實(shí)還是對(duì)RDD的操作。defprint(num: Int): Unit = ssc.withScope {

defforeachFunc: (RDD[T], Time) => Unit = {

(rdd: RDD[T], time: Time) => {

valfirstNum = rdd.take(num + 1)

// scalastyle:off println

println("-------------------------------------------")

println("Time: " + time)

println("-------------------------------------------")

firstNum.take(num).foreach(println)

if(firstNum.length > num)println("...")

println()

}

}

foreachRDD(context.sparkContext.clean(foreachFunc), displayInnerRDDOps =false)

}

上邊代碼中foreachFunc這個(gè)方法是對(duì)Dstream action級(jí)別的方法的進(jìn)一步封裝,增加了如下代碼,在運(yùn)行spark streaming程序時(shí)對(duì)這些輸出很熟悉。

println("-------------------------------------------")

println("Time: " + time)

println("-------------------------------------------")

foreachRDD方法,轉(zhuǎn)過來new ForEachDstream

Apply a function to each RDD in this DStream. This is an output operator, so

* 'this' DStream will be registered as an output stream and therefore materialized.

private defforeachRDD(

foreachFunc: (RDD[T], Time) => Unit,

displayInnerRDDOps: Boolean): Unit = {

newForEachDStream(this,

context.sparkContext.clean(foreachFunc,false), displayInnerRDDOps).register()

}

注釋中說的:將這個(gè)函數(shù)作用于這個(gè)Dstream中的每一個(gè)RDD,這是一個(gè)輸出操作,因此這個(gè)Dstream會(huì)被注冊(cè)成outputstream,并進(jìn)行物化。

ForEachDstream中很重要的一個(gè)函數(shù)generateJob。考慮時(shí)間維度和action級(jí)別,每個(gè)Duration都基于generateJob來生成作業(yè)。foreachFunc(rdd, time)//這個(gè)方法就是對(duì)Dstream最后的操作 ,newJob(time, jobFunc)只是在RDD的基礎(chǔ)上,加上時(shí)間維度的封裝而已。這里的Job只是一個(gè)普通的對(duì)象,代表了一個(gè)spark的計(jì)算,調(diào)用Job的run方法時(shí),真正的作業(yè)就觸發(fā)了。foreachFunc(rdd, time)中的rdd其實(shí)就是通過DstreamGraph中最后一個(gè)Dstream來決定的。

override defgenerateJob(time: Time): Option[Job] = {

parent.getOrCompute(time)match{

caseSome(rdd) =>

valjobFunc = () => createRDDWithLocalProperties(time, displayInnerRDDOps) {

foreachFunc(rdd, time)

}

Some(newJob(time, jobFunc))

caseNone => None

}

}

Jon是通過ForEachDstream的generateJob來生成的,值得注意的是在Dstream的子類中,只有ForEachDstream重寫了generateJob方法。

現(xiàn)在考慮一下ForEachDstream的generateJob方法是誰調(diào)用的?當(dāng)然是JobGenerator。ForEachDstream的generateJob方法是靜態(tài)的邏輯級(jí)別,他如果想要真正運(yùn)行起來變成物理級(jí)別的這時(shí)候就需要JobGenerator。

現(xiàn)在就來看看JobGenerator的代碼,JobGenerator中有一個(gè)定時(shí)器timer和消息循環(huán)體eventloop,timer會(huì)基于batchinteval,一直向eventloop中發(fā)送JenerateJobs的消息,進(jìn)而導(dǎo)致processEvent方法->generateJobs方法的執(zhí)行。

private valtimer=newRecurringTimer(clock,ssc.graph.batchDuration.milliseconds,

longTime =>eventLoop.post(GenerateJobs(newTime(longTime))), "JobGenerator")

eventLoop =newEventLoop[JobGeneratorEvent]("JobGenerator") {

override protected defonReceive(event: JobGeneratorEvent): Unit = processEvent(event)

override protected defonError(e: Throwable): Unit = {

jobScheduler.reportError("Error in job generator", e)

}

}

generateJobs方法的代碼:

private defgenerateJobs(time: Time) {

SparkEnv.set(ssc.env)

Try{

jobScheduler.receiverTracker.allocateBlocksToBatch(time)

graph.generateJobs(time) // generate jobs using allocated block

graph.generateJobs(time)這個(gè)方法的代碼:

defgenerateJobs(time: Time): Seq[Job] = {

logDebug("Generating jobs for time " + time)

valjobs =this.synchronized {

outputStreams.flatMap { outputStream =>

valjobOption = outputStream.generateJob(time)

jobOption.foreach(_.setCallSite(outputStream.creationSite))

jobOption

}

}

logDebug("Generated " + jobs.length + " jobs for time " + time)

jobs

}

其中的outputStream.generateJob(time)中的outputStream就是前面說ForEachDstream,generateJob(time)方法就是ForEachDstream中的generateJob(time)方法。

這是從時(shí)間維度調(diào)用空間維度的東西,所以時(shí)空結(jié)合就轉(zhuǎn)變成物理的執(zhí)行了。

再來看看JobGenerator的generateJobs方法:

Try{

jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch

graph.generateJobs(time) // generate jobs using allocated block

}match{

caseSuccess(jobs) =>

valstreamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time)

jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))

caseFailure(e) =>

jobScheduler.reportError("Error generating jobs for time " + time, e)

}

eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater =false))

基于graph.generateJobs產(chǎn)生job后,會(huì)封裝成JobSet并提交給JobScheduler,JobSet(time, jobs, streamIdToInputInfos),其中streamIdToInputInfos就是接收的數(shù)據(jù)的元數(shù)據(jù)。

JobSet代表了一個(gè)batch duration中的一批jobs。就是一個(gè)普通對(duì)象,包含了未提交的jobs,提交的時(shí)間,執(zhí)行開始和結(jié)束時(shí)間等信息。

JobSet提交給JobScheduler后,會(huì)放入jobSets數(shù)據(jù)結(jié)構(gòu)中,jobSets.put(jobSet.time, jobSet) ,所以JobScheduler就擁有了每個(gè)batch中的jobSet.在線程池中進(jìn)行執(zhí)行。

defsubmitJobSet(jobSet: JobSet) {

if(jobSet.jobs.isEmpty) {

logInfo("No jobs added for time " + jobSet.time)

}else{

listenerBus.post(StreamingListenerBatchSubmitted(jobSet.toBatchInfo))

jobSets.put(jobSet.time, jobSet)

jobSet.jobs.foreach(job =>jobExecutor.execute(newJobHandler(job)))

logInfo("Added jobs for time " + jobSet.time)

}

}

在把job放入線程池中時(shí),采用JonHandler進(jìn)行封裝。JonHandler是一個(gè)Runable接口的實(shí)例。

其中主要的代碼就是job.run(),前面說過job.run()調(diào)用的就是Dstream的action級(jí)別的方法。

在job.run()前后會(huì)發(fā)送JonStarted和JobCompleted的消息,JobScheduler接收到這兩個(gè)消息只是記錄一下時(shí)間,通知一下job要開始執(zhí)行或者執(zhí)行完成,并沒有過多的操作。

_eventLoop.post(JobStarted(job,clock.getTimeMillis()))

PairRDDFunctions.disableOutputSpecValidation.withValue(true) {

job.run()

}

_eventLoop =eventLoop

if(_eventLoop !=null) {

_eventLoop.post(JobCompleted(job,clock.getTimeMillis()))

}

備注:

資料來源于:DT_大數(shù)據(jù)夢(mèng)工廠(Spark發(fā)行版本定制)

更多私密內(nèi)容,請(qǐng)關(guān)注微信公眾號(hào):DT_Spark

如果您對(duì)大數(shù)據(jù)Spark感興趣,可以免費(fèi)聽由王家林老師每天晚上20:00開設(shè)的Spark永久免費(fèi)公開課,地址YY房間號(hào):68917580

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容