接着上一节,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的真正执行!

第7课:Spark Streaming源码解读之JobScheduler内幕实现和深度思考_第1张图片



备注:

1、DT大数据梦工厂微信公众号DT_Spark 
2、IMF晚8点大数据实战YY直播频道号:68917580
3、新浪微博: http://www.weibo.com/ilovepains