SparkStreaming的WordCount示例及源码分析(三)

Spark 专栏收录该内容
11 篇文章 0 订阅

  在JobScheduler的start中,当receiverTracker启动完毕之后,将启动JobGenerator。JobGenerator负责对DstreamGraph的初始化,DStream与RDD的转换,生成Job,提交执行等工作.

/** Start generation of jobs */
def start(): Unit = synchronized {
  if (eventLoop != null) return // generator has already been started

  // Call checkpointWriter here to initialize it before eventLoop uses it to avoid a deadlock.
  // See SPARK-10125
  checkpointWriter

  eventLoop = new EventLoop[JobGeneratorEvent]("JobGenerator") {
    override protected def onReceive(event: JobGeneratorEvent): Unit = processEvent(event)

    override protected def onError(e: Throwable): Unit = {
      jobScheduler.reportError("Error in job generator", e)
    }
  }
  eventLoop.start()

  if (ssc.isCheckpointPresent) {
    restart()
  } else {
    startFirstTime()
  }
}

  可以看到,首先会启动EventLoop[JobGeneratorEvent]来处理各类JobGeneratorEvent。之后会根据是否是第一次启动,也就是是否存在checkpoint,来具体的决定是restart()还是startFirstTime()。
  JobGenerator维护了一个定时器,周期就是之前设置的batchDuration,定时为每个batch生成RDD DAG的实例。

  private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,
    longTime => eventLoop.post(GenerateJobs(new Time(longTime))), "JobGenerator")

  不论是restart()还是startFirstTime()都会启动这个定时器,每当周期到了之后,就会给JobGenerator自身的eventLoop发送一个GenerateJobs消息,eventLoop收到消息后会调用
processEvent(event):

  /** Processes all events */
  private def processEvent(event: JobGeneratorEvent) {
    logDebug("Got event " + event)
    event match {
      case GenerateJobs(time) => generateJobs(time)
      case ClearMetadata(time) => clearMetadata(time)
      case DoCheckpoint(time, clearCheckpointDataLater) =>
        doCheckpoint(time, clearCheckpointDataLater)
      case ClearCheckpointData(time) => clearCheckpointData(time)
    }
  }
  /** Generate jobs and perform checkpoint for the given `time`.  */
  private def generateJobs(time: Time) {
    // Set the SparkEnv in this thread, so that job generation code can access the environment
    // Example: BlockRDDs are created in this thread, and it needs to access BlockManager
    // Update: This is probably redundant after threadlocal stuff in SparkEnv has been removed.
    SparkEnv.set(ssc.env)
    Try {
      jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch
      graph.generateJobs(time) // generate jobs using allocated block
    } match {
      case Success(jobs) =>
        val streamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time)
        jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))
      case Failure(e) =>
        jobScheduler.reportError("Error generating jobs for time " + time, e)
    }
    eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = false))
  }

  当消息是GenerateJobs(time) 时,将调用generateJobs(time),generateJobs完成以下5个任务:
  jobScheduler.receiverTracker.allocateBlocksToBatch(time)是要求ReceiverTracker将目前已收到数据的meta信息进行一次allocate,即将上次batch切分后剩余的数据切分到到本次新的batch里。每个块数据的meta信息,将被划入一个、且只被划入一个batch。
  graph.generateJobs(time)要求DStreamGraph复制出一套新的RDD DAG的实例,返回一个 Seq[Job]。
  jobScheduler.inputInfoTracker.getInfo(time)获取之前ReceiverTracker分配到本batch的源头数据的meta信息。
  jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))将time、生成的本batch的RDD DAG、获取到的meta信息封装成JobSet提交给JobScheduler。这里的向 JobScheduler提交过程与JobScheduler接下来在jobExecutor里执行过程是异步的,因此本步将非常快即可返回。
  最后只要提交结束,就马上对整个系统的当前运行状态做一个checkpoint。这里做 checkpoint 也只是异步提交一个 DoCheckpoint 消息请求,不用等checkpoint真正写完成即可返回。

那么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 => jobExecutor.execute(new JobHandler(job)),也就是将每个 job都在 jobExecutor 线程池中、用new JobHandler来处理。JobHandler实质上是Runnable对象,其run()中除了做一些状态记录外,最主要的就是调用job.run()。
  jobExecutor的定义如下:

  private val numConcurrentJobs = ssc.conf.getInt("spark.streaming.concurrentJobs", 1)
  private val jobExecutor =
    ThreadUtils.newDaemonFixedThreadPool(numConcurrentJobs, "streaming-job-executor")

  这里jobExecutor的线程池大小,是由spark.streaming.concurrentJobs参数来控制的,当没有显式设置时,其取值为1。这里jobExecutor的线程池大小,就是能够并行执行的Job数。可以如下设置:

val conf = new SparkConf()
    conf.setMaster("local[2]")
    conf.set("spark.streaming.blockInterval", s"${BLOCK_INTERVAL}s")
    conf.set("spark.streaming.concurrentJobs", s"${CURRENT_JOBS}")

三.总结
  在StreamingContext调用start方法的内部其实是会启动JobScheduler的Start方法,进行消息循环,在JobScheduler的start内部会构造JobGenerator和ReceiverTacker,并且调用JobGenerator和ReceiverTacker的start方法:
  JobGenerator启动后会不断的根据batchDuration生成一个个的Job。
  ReceiverTracker启动后首先在Spark Cluster中启动Receiver(其实是在Executor中先启动ReceiverSupervisor),在Receiver收到数据后会通过ReceiverSupervisor存储到Executor并且把数据的Metadata信息发送给Driver中的ReceiverTracker,在ReceiverTracker内部会通过ReceivedBlockTracker来管理接收到的元数据信息。
  每个BatchInterval会产生一个具体的Job,其实这里的Job不是Spark Core中所指的Job,它只是基于DStreamGraph而生成的RDD的DAG而已,从Java角度讲,相当于Runnable接口实例,此时要想运行Job需要提交给JobScheduler,在JobScheduler中通过线程池的方式找到一个单独的线程来提交Job到集群运行(其实是在线程中基于RDD的Action触发真正的作业的运行)。


SparkStreaming的WordCount示例及源码分析(一)
SparkStreaming的WordCount示例及源码分析(二)
SparkStreaming的WordCount示例及源码分析(三)

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值