SparkStream例子HdfsWordCount--Streaming的Job是如何调度的

上一篇“SparkStream例子HdfsWordCount--从Dstream到RDD全过程解析”解析了每个Dstream周期内,是如何生成的RDD的。

该篇描述一下RDD变成Streaming的Job之后,如何到Executor上面执行开发者写的foreachFunc(rdd,time)的过程。

四、     Streaming的Job是如何进行调度执行的?

1, 在DstreamGraph的generateJobs()中通过调用ForeachInputDstream.generateJob(time)将time这个周期内的所有数据放到UnionRdd中,

2, 同时和开发者写的业务代码放在Some(newJob(time, jobFunc)) 这个Steaming的Job里面的jobFunc中。然后返回jobOption

def generateJobs(time: Time): Seq[Job] = {
  logDebug("Generating jobs for time " + time)
  val jobs = this.synchronized {
    outputStreams.flatMap { outputStream =>
      //jobOption返回就是Some(new Job(time, jobFunc)),如果是调用的print()方法,则jobFunc就是print方法中声明的方法
      val jobOption = outputStream.generateJob(time)
      jobOption.foreach(_.setCallSite(outputStream.creationSite))
      jobOption
    }
  }
  logDebug("Generated " + jobs.length + " jobs for time " + time)
  jobs
}

3,按上一篇的执行流程,DstreamGraph的generateJobs()执行完成之后,返回JobGenerator.generateJobs(time)继续往下执行

/** Generate jobs and perform checkpoint for the given `time`.  */
private def generateJobs(time: Time) {
  
  SparkEnv.set(ssc.env)
  Try {
    jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch
    //调用graph的generateJobs方法,通过scala的Try的apply函数,返回Success(jobs) 或者 Failure(e), 其中的jobs就是该方法返回的Job对象集合,如果Job创建成功,再调用JobScheduler的submitJobSet方法将job提交给集群执行。
    graph.generateJobs(time) // generate jobs using allocated block
  } match {
    case Success(jobs) =>
      val streamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time)
      //其中streamIdToInputInfos就是接收的数据的元数据
      //JobSet代表了一个batch duration中的一批jobs。就是一个普通对象,包含了未提交的jobs,提交的时间,执行开始和结束时间等信息。
      jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))
    case Failure(e) =>
      jobScheduler.reportError("Error generating jobs for time " + time, e)
  }
  //发送执行CheckPoint时间,发送周期为streaming batch接收数据的时间
  eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = false))
}

 

4、上面执行成功之后会转入JobScheduler.submitJobSet方法中

class JobScheduler(val ssc: StreamingContext) extends Logging {
  private val jobSets: java.util.Map[Time, JobSet] = new ConcurrentHashMap[Time, JobSet]
  private val numConcurrentJobs = ssc.conf.getInt("spark.streaming.concurrentJobs", 1)
  //JobScheduler在实例化的时候会实例化JobGenerator和线程池。
  private val jobExecutor =
    ThreadUtils.newDaemonFixedThreadPool(numConcurrentJobs, "streaming-job-executor")

==》其中jobExecutor就是Jdk的Executor线程池。

==》上面的代码可以看。线程池中默认是有一条线程,可以在spark配置文件中配置或者使用代码在sparkconf中修改默认的线程数,在一定程度上增加默认线程数可以提高执行Job的效率,这也是一个性能调优的方法(尤其是在一个程序中有多个Job时)

 

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放在jobSets的ConcurrentHashMap中
    jobSet.jobs.foreach(job => jobExecutor.execute(new JobHandler(job)))
    logInfo("Added jobs for time " + jobSet.time)
  }
}

5,JobExecutor是线程池,说明JobHandler肯定是Runnable实现类:

private class JobHandler(job: Job) extends Runnable with Logging {
    import JobScheduler._
    def run() {
      try {
        。。。。

        var _eventLoop = eventLoop
        if (_eventLoop != null) {
//从这个代码可知,在当前周期内,Rdd数据Job之后,才将JobStarted放到EventLoop的成员队列中,开始执行EventLoop中的onReceive方法,即执行JobScheduler中processEvent()对应的handleJobStart()
          _eventLoop.post(JobStarted(job, clock.getTimeMillis()))
。。。。。
    }
  }

 

private def processEvent(event: JobSchedulerEvent) {
  try {
    event match {
      case JobStarted(job, startTime) => handleJobStart(job, startTime)
      case JobCompleted(job, completedTime) => handleJobCompletion(job, completedTime)
      case ErrorReported(m, e) => handleError(m, e)
    }
  } catch {
    case e: Throwable =>
      reportError("Error in job scheduler", e)
  }
}

6、实际上这个handleJobStart也没有什么神秘的,就是记录一下启动事件而以:

//捕获job的启动事件
private def handleJobStart(job: Job, startTime: Long) {
  val jobSet = jobSets.get(job.time)
  val isFirstJobOfJobSet = !jobSet.hasStarted
  jobSet.handleJobStart(job)
  if (isFirstJobOfJobSet) {
    // "StreamingListenerBatchStarted" should be posted after calling "handleJobStart" to get the
    // correct "jobSet.processingStartTime".
    //listenerBus它是StreamingListenerBus对象
    listenerBus.post(StreamingListenerBatchStarted(jobSet.toBatchInfo))
  }
  job.setStartTime(startTime)
  listenerBus.post(StreamingListenerOutputOperationStarted(job.toOutputOperationInfo))
  logInfo("Starting job " + job.id + " from job set of time " + jobSet.time)
}

7,再返回到JobHandler这个Runnable类中查看一下Job.run到底干了什么了?

 

  private class JobHandler(job: Job) extends Runnable with Logging {
    import JobScheduler._

    def run() {
      try {
       。。。。
        var _eventLoop = eventLoop
        if (_eventLoop != null) {
          _eventLoop.post(JobStarted(job, clock.getTimeMillis()))
          PairRDDFunctions.disableOutputSpecValidation.withValue(true) {
            job.run()
          }
          _eventLoop = eventLoop
          if (_eventLoop != null) {
            _eventLoop.post(JobCompleted(job, clock.getTimeMillis()))
          }
        } else {
          // JobScheduler has been stopped.
        }
      } finally {
        ssc.sc.setLocalProperty(JobScheduler.BATCH_TIME_PROPERTY_KEY, null)
        ssc.sc.setLocalProperty(JobScheduler.OUTPUT_OP_ID_PROPERTY_KEY, null)
      }
    }
  }

8,Job.run()可以很清晰的看出实际上是执行的ForEachDstream

//          val jobFunc = () =>createRDDWithLocalProperties(time, displayInnerRDDOps) {
//            foreachFunc(rdd, time)
//          }

private[streaming]
class Job(val time: Time, func: () => _) {
….
  private var _result: Try[_] = null
…
  def run() {
    _result = Try(func())
  }

===》而ForEachDstream中的foreachFunc(rdd, time)不就是咱们在案例中看到的调用print代码中声明的foreachFunc吗。这边的foreachFunc里面的调用就是sparkCore的内容了

def print(num: Int): Unit = ssc.withScope {
  def foreachFunc: (RDD[T], Time) => Unit = {
    (rdd: RDD[T], time: Time) => {
      val firstNum = rdd.take(num + 1)
      // scalastyle:off println
      println("-------------------------------------------")
      println("Time: " + time)
      println("-------------------------------------------")
      firstNum.take(num).foreach(println)
      if (firstNum.length > num) println("...")
      println()
      // scalastyle:on println
    }
  }
  foreachRDD(context.sparkContext.clean(foreachFunc), displayInnerRDDOps = false)
}

===》再回到JobHandler,当Job.run方法执行结束之后,就会发送JobCompleted的case class给EventLoop。

==== 》至此,当前Time周期内的Job调度完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值