Spark-Streaming checkpoint的原理

来源:互联网 发布:深孔钻编程 编辑:程序博客网 时间:2024/05/17 02:29

    本文以KafkaDirectDStream方式为例说明Spark-Streaming checkpoint的原理

    JobGenrerator.generateJobs负责Streaming Job的产生,产生并且提交执行Job之后,会发送DoCheckpoint事件,源码如下:

[java] view plain copy
  1. private def generateJobs(time: Time) {  
  2.     // Set the SparkEnv in this thread, so that job generation code can access the environment  
  3.     // Example: BlockRDDs are created in this thread, and it needs to access BlockManager  
  4.     // Update: This is probably redundant after threadlocal stuff in SparkEnv has been removed.  
  5.     SparkEnv.set(ssc.env)  
  6.     Try {  
  7.       jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch  
  8.       graph.generateJobs(time) // generate jobs using allocated block  
  9.     } match {  
  10.       case Success(jobs) =>  
  11.         val streamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time)  
  12.         val streamIdToNumRecords = streamIdToInputInfos.mapValues(_.numRecords)  
  13.         jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToNumRecords))//提交Job执行  
  14.       case Failure(e) =>  
  15.         jobScheduler.reportError("Error generating jobs for time " + time, e)  
  16.     }  
  17.     eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = false))//发送执行CheckPoint时间,发送周期为streaming batch接收数据的时间  
  18.   }  

从上面代码可知道,每次产生Streaming Job都会触发Checkpoint的执行


    JobGenerator.processEvent方法接收到DoCheckpoint事件后,调用JobGenerator.doCheckpoint方法进行Checkpoint处理

    JobGenerator.doCheckpoint方法调用DStreamGraph.updateCheckpointData对输出DStream进行Checkpoint,

然后调用CheckpointWriter将Checkpoint信息写到Checkpoint目录,源码如下:

[java] view plain copy
  1. private def doCheckpoint(time: Time, clearCheckpointDataLater: Boolean) {  
  2.     if (shouldCheckpoint && (time - graph.zeroTime).isMultipleOf(ssc.checkpointDuration)) {  
  3.       logInfo("Checkpointing graph for time " + time)  
  4.       ssc.graph.updateCheckpointData(time)//输出DStream进行Checkpoint  
  5.       checkpointWriter.write(new Checkpoint(ssc, time), clearCheckpointDataLater)//将Checkpoint信息写到Checkpoint目录  
  6.     }  
  7.   }  

下面来看看如何对DStream进行Checkpoint的

[java] view plain copy
  1. def updateCheckpointData(time: Time) {  
  2.     logInfo("Updating checkpoint data for time " + time)  
  3.     this.synchronized {  
  4.       outputStreams.foreach(_.updateCheckpointData(time))//将输出流中的每个DStream信息转化成相应的Checkpoint信息  
  5.     }  
  6.     logInfo("Updated checkpoint data for time " + time)  
  7.   }  

可见DStreamGraph.updateCheckpointData方法所作的工作是将输出流中的每个DStream信息转化成相应的Checkpoint信息

对每个DStream信息转化成Checkpoint发生在DStream.updateCheckpointData方法,这个方法更新DStream的Checkpoint信息,并且更新DStream依赖的所有DStream的Checkpoint信息,源码如下:

[java] view plain copy
  1. private[streaming] def updateCheckpointData(currentTime: Time) {  
  2.     logDebug("Updating checkpoint data for time " + currentTime)  
  3.     checkpointData.update(currentTime)//更新DStream的Checkpoint信息  
  4.     dependencies.foreach(_.updateCheckpointData(currentTime))//更新所有依赖的DStream的Checkpoint信息  
  5.     logDebug("Updated checkpoint data for time " + currentTime + ": " + checkpointData)  
  6.   }  


DirectKafkaInputDStreamCheckpointData的Checkpoint信息更新如下:

[java] view plain copy
  1. def batchForTime: mutable.HashMap[Time, Array[(String, Int, Long, Long)]] = {  
  2.       data.asInstanceOf[mutable.HashMap[Time, Array[OffsetRange.OffsetRangeTuple]]]  
  3.     }  
  4.   
  5.     override def update(time: Time) {  
  6.       batchForTime.clear()//删除老的Checkpoint信息  
  7.       generatedRDDs.foreach { kv =>  
  8.         val a = kv._2.asInstanceOf[KafkaRDD[K, V, U, T, R]].offsetRanges.map(_.toTuple).toArray//a是一个数组,数组的一个元素是一个RDD的所有分区信息,一个分区信息包含了这个分区的起始数据和终止数据在Kafka的某个分区的offset  
  9.         batchForTime += kv._1 -> a//将分区信息存储在DirectKafkaInputDStreamCheckpointData.data中  
  10.       }  
  11.     }  


在Spark中,将所有没有成功完成的Job放在了JobScheduler.jobSets中,Job成功完成之后再将它从JobScheduler.jobSets删除,源码如下:

[java] view plain copy
  1. def submitJobSet(jobSet: JobSet) {  
  2.   if (jobSet.jobs.isEmpty) {  
  3.     logInfo("No jobs added for time " + jobSet.time)  
  4.   } else {  
  5.     listenerBus.post(StreamingListenerBatchSubmitted(jobSet.toBatchInfo))  
  6.     jobSets.put(jobSet.time, jobSet)//将任务放到了jobSets  
  7.     jobSet.jobs.foreach(job => jobExecutor.execute(new JobHandler(job)))  
  8.     logInfo("Added jobs for time " + jobSet.time)  
  9.   }  
  10. }  
[java] view plain copy
  1. private def handleJobCompletion(job: Job) {  
  2.   job.result match {  
  3.     case Success(_) =>  
  4.       val jobSet = jobSets.get(job.time)  
  5.       jobSet.handleJobCompletion(job)  
  6.       logInfo("Finished job " + job.id + " from job set of time " + jobSet.time)  
  7.       if (jobSet.hasCompleted) {  
  8.         jobSets.remove(jobSet.time)//从任务等待队列中删除这个任务  
  9.         jobGenerator.onBatchCompletion(jobSet.time)  
  10.         logInfo("Total delay: %.3f s for time %s (execution: %.3f s)".format(  
  11.           jobSet.totalDelay / 1000.0, jobSet.time.toString,  
  12.           jobSet.processingDelay / 1000.0  
  13.         ))  
  14.         listenerBus.post(StreamingListenerBatchCompleted(jobSet.toBatchInfo))  
  15.       }  
  16.     case Failure(e) =>  
  17.       reportError("Error running job " + job, e)  
  18.   }  
  19. }  

Checkpoint.graph对应于Spark-streaming应用的DStreamGraph,DStreamGraph.outputStreams包含了要Checkpoint的DStream信息。Checkpoint.pendingTimes对应没有成功完成的Job,因此在将Checkpoint信息保存到HDFS的时候,这些信息都会被Checkpoint。

要想上一次Spark-streaming Application产生的Checkpoint信息有用,在创建StreamingContext的时候,必须要传入Checkpoint信息。上一次Spark-streaming Application产生的Checkpoint信息的读取可以通过调用CheckpointReader.read方法。

如果创建StreamingContext传入上次执行产生的Checkpoint信息则会使用Checkpoint包含的DStreamGraph作为本次Application的DStreamGraph,它里面包含了需要Checkpoint的DStream信息。然后根据DStreamGraph恢复上一次执行时的DStream信息。源码如下:

[java] view plain copy
  1. private[streaming] val graph: DStreamGraph = {  
  2.     if (isCheckpointPresent) {  
  3.       cp_.graph.setContext(this)//使用Checkpoint信息里面的DStreamGraph作为本次Application的DStreamGraph,它里面包含了需要Checkpoint的DStream信息  
  4.       cp_.graph.restoreCheckpointData()//恢复上一次执行时的DStream信息  
  5.       cp_.graph  
  6.     } else {  
  7.       require(batchDur_ != null"Batch duration for StreamingContext cannot be null")  
  8.       val newGraph = new DStreamGraph()  
  9.       newGraph.setBatchDuration(batchDur_)  
  10.       newGraph  
  11.     }  
  12.   }  

JobGenerator.start开始Streaming Job的产生,如果存在Checkpoint信息,则调用JobGenerator.restart开始Spark-streaming Job的执行,在这个方法里面会将上一次Application执行时已经产生但是还没有成功执行完成的Streaming Job先恢复出来,然后再把从崩溃到重新执行的时间之间没有产生Job补上,然后让Spark先执行这些丢失的Job,源码如下:

[java] view plain copy
  1. def start(): Unit = synchronized {  
  2.     if (eventLoop != nullreturn // generator has already been started  
  3.   
  4.     eventLoop = new EventLoop[JobGeneratorEvent]("JobGenerator") {  
  5.       override protected def onReceive(event: JobGeneratorEvent): Unit = processEvent(event)  
  6.   
  7.       override protected def onError(e: Throwable): Unit = {  
  8.         jobScheduler.reportError("Error in job generator", e)  
  9.       }  
  10.     }  
  11.     eventLoop.start()  
  12.   
  13.     if (ssc.isCheckpointPresent) {  
  14.       restart()  
  15.     } else {  
  16.       startFirstTime()  
  17.     }  
  18.   }  


[java] view plain copy
  1. private def restart() {  
  2.     // If manual clock is being used for testing, then  
  3.     // either set the manual clock to the last checkpointed time,  
  4.     // or if the property is defined set it to that time  
  5.     if (clock.isInstanceOf[ManualClock]) {  
  6.       val lastTime = ssc.initialCheckpoint.checkpointTime.milliseconds  
  7.       val jumpTime = ssc.sc.conf.getLong("spark.streaming.manualClock.jump"0)  
  8.       clock.asInstanceOf[ManualClock].setTime(lastTime + jumpTime)  
  9.     }  
  10.   
  11.     val batchDuration = ssc.graph.batchDuration  
  12.   
  13.     // Batches when the master was down, that is,  
  14.     // between the checkpoint and current restart time  
  15.     val checkpointTime = ssc.initialCheckpoint.checkpointTime  
  16.     val restartTime = new Time(timer.getRestartTime(graph.zeroTime.milliseconds))  
  17.     val downTimes = checkpointTime.until(restartTime, batchDuration)  
  18.     logInfo("Batches during down time (" + downTimes.size + " batches): "  
  19.       + downTimes.mkString(", "))  
  20.   
  21.     // Batches that were unprocessed before failure  
  22.     val pendingTimes = ssc.initialCheckpoint.pendingTimes.sorted(Time.ordering)//上一次Application执行时已经产生但是还没有成功执行完成的Streaming Job先恢复出来  
  23.     logInfo("Batches pending processing (" + pendingTimes.size + " batches): " +  
  24.       pendingTimes.mkString(", "))  
  25.     // Reschedule jobs for these times  
  26.     val timesToReschedule = (pendingTimes ++ downTimes).distinct.sorted(Time.ordering)//崩溃到重新执行的时间之间没有产生Job补上  
  27.     logInfo("Batches to reschedule (" + timesToReschedule.size + " batches): " +  
  28.       timesToReschedule.mkString(", "))  
  29.     timesToReschedule.foreach { time =>  
  30.       // Allocate the related blocks when recovering from failure, because some blocks that were  
  31.       // added but not allocated, are dangling in the queue after recovering, we have to allocate  
  32.       // those blocks to the next batch, which is the batch they were supposed to go.  
  33.       jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch  
  34.       jobScheduler.submitJobSet(JobSet(time, graph.generateJobs(time)))//先执行丢失的Job  
  35.     }  
  36.   
  37.     // Restart the timer  
  38.     timer.start(restartTime.milliseconds)//进入新任务的产生  
  39.     logInfo("Restarted JobGenerator at " + restartTime)  
  40.   }  
0 0