这段时间分析了下spark-streming任务调度的源码,成果分享一下。
类似于spark-core,spark-streaming有自己的一套任务调度,具体代码在spark-streaming的scheduler包里面。
我以Kafka DirectDStream数据接收为例,画了一张整体的流程图:
在将这张图之前,需要先明确spark-streaming中3个重要的类:
JobScheduler类,这个类负责spark-streaming的Job调度
JobGenerator类, 这个类负责spark-streaming的Job生成
DStreamGraph类, 这个类负责spark-streaming的Job的通用描述,JobGenerator类根据DStreamGraph类的描述产生Job,比如说设置Job的SparkContext、设置每个outputStream的初始化
Driver程序在创建StreamContext对象的时候会创建JobScheduler对象和DStreamGraph对象,执行StreamContext.start之后,spark-streaming进入了它的任务调度,
def start(): Unit = synchronized { state match { case INITIALIZED => startSite.set(DStream.getCreationSite()) sparkContext.setCallSite(startSite.get) StreamingContext.ACTIVATION_LOCK.synchronized { StreamingContext.assertNoOtherContextIsActive() try { validate() <span style="color:#ff0000;"><strong>scheduler.start()//进入spark-streaming的Job调度 state = StreamingContextState.ACTIVE } catch { case NonFatal(e) => logError("Error starting the context, marking it as stopped", e) scheduler.stop(false) state = StreamingContextState.STOPPED throw e } StreamingContext.setActiveContext(this) }
JobScheduler的start方法里面,会启动任务任务调度的事件侦听循环,侦听Job开始事件、Job完成事件、 Job出错事件3类事件。在spark-streaming DirectDStream数据接收方式下,ReceiverTracker几乎没有什么作用。这个方法里面最终要的是启动JobGenerator.start()方法的调用。这个方法的调用进入了Spark-streaming的Job的产生
def start(): Unit = synchronized { if (eventLoop != null) return // scheduler has already been started logDebug("Starting JobScheduler") eventLoop = new EventLoop[JobSchedulerEvent]("JobScheduler") { override protected def onReceive(event: JobSchedulerEvent): Unit = processEvent(event) override protected def onError(e: Throwable): Unit = reportError("Error in job scheduler", e) } eventLoop.start() listenerBus.start(ssc.sparkContext) receiverTracker = new ReceiverTracker(ssc)//在DirectDStream接收方式下,几乎没有什么作用,因为DirectStream已经不使用receiver了 inputInfoTracker = new InputInfoTracker(ssc) receiverTracker.start() jobGenerator.start()//进入spark-streaming Job的产生 logInfo("Started JobScheduler") }
1. 启动侦听Job产生事件死循环
2. 第一次产生Job
def start(): Unit = synchronized { if (eventLoop != null) return // generator has already been started 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()//开始侦听spark-streaming产生Job事件 if (ssc.isCheckpointPresent) { restart() } else { startFirstTime()//第一次启动Job } }
private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,//定时产生Job的时间周期是设置的streaming batch时间 longTime => eventLoop.post(GenerateJobs(new Time(longTime))), "JobGenerator")
对比JobScheduler的事件,可以得出如下结论:
JobScheduler的事件,处理Job的调度; JobGenerator的事件,更倾向于处理Job内部的一些事情
JobGenerator.startFirstTime方法启动第一次Job的生成和启动定时产生Job的定时器
/** Starts the generator for the first time */ private def startFirstTime() { val startTime = new Time(timer.getStartTime()) graph.start(startTime - graph.batchDuration)//启动DStreamGraph的初始化 timer.start(startTime.milliseconds) //启动定时产生Streaming Job的定时器 logInfo("Started JobGenerator at " + startTime) }
def start(time: Time) { this.synchronized { if (zeroTime != null) { throw new Exception("DStream graph computation already started") } zeroTime = time startTime = time outputStreams.foreach(_.initialize(zeroTime)) outputStreams.foreach(_.remember(rememberDuration)) outputStreams.foreach(_.validateAtStart) inputStreams.par.foreach(_.start())//非DirectDStream数据接收</strong></span>情况下,开始接收数据 } }
DStreamGraph.outputStreams的作用:当数据需要输出的时候,会将inputStream添加到outputStreams,比如说调用DStream.foreachRDD输出数据的时候
spark-streaming Job的具体生成在JobGenerator.generateJobs方法。
关于spark-streaming Job如何具体生成,参见接下来的 spark-streaming的Job调度 下