接着上一節,JobGenerator每隔batchInterval時間會動態的生成JobSet提交給JobScheduler。JobScheduler接收到JobSet後,如何處理呢?
def submitJobSet(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(new JobHandler(job))) logInfo("Added jobs for time " + jobSet.time) } }
這裏會爲每個job生成一個新的JobHandler,交給jobExecutor運行。
private val numConcurrentJobs = ssc.conf.getInt("spark.streaming.concurrentJobs", 1) private val jobExecutor = ThreadUtils.newDaemonFixedThreadPool(numConcurrentJobs, "streaming-job-executor") // 線程池
jobExecutor是一個線程池,線程的個數由參數配置。如果需要多個job同時運行,比如在同一個batchInterval中有多個output,則需要配置該參數。
這裏最重要的處理邏輯是 job => jobExecutor.execute(new JobHandler(job)),也就是將每個 job 都在 jobExecutor 線程池中、用 new JobHandler 來處理。
先來看JobHandler針對Job的主要處理邏輯:
...... if (_eventLoop != null) { _eventLoop.post(JobStarted(job, clock.getTimeMillis())) // Disable checks for existing output directories in jobs launched by the streaming // scheduler, since we may need to write output to an existing directory during checkpoint // recovery; see SPARK-4835 for more details. PairRDDFunctions.disableOutputSpecValidation.withValue(true) { job.run() } _eventLoop = eventLoop if (_eventLoop != null) { _eventLoop.post(JobCompleted(job, clock.getTimeMillis())) } ......
也就是說,JobHandler除了做一些狀態記錄外,最主要的就是調用job.run()!這裏就與我們在 DStream 生成 RDD 實例詳解 裏分析的對應起來了: 在ForEachDStream.generateJob(time)時,是定義了Job的運行邏輯,即定義了Job.func。而在JobHandler這裏,是真正調用了Job.run()、將觸發Job.func的真正執行!
備註:
1、DT大數據夢工廠微信公衆號DT_Spark
2、IMF晚8點大數據實戰YY直播頻道號:68917580
3、新浪微博: http://www.weibo.com/ilovepains