原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/)

本期内容:

一、Spark Streaming 数据清理总览

二、Spark Streaming 数据清理过程详解

三、Spark Streaming 数据清理的触发机制


Spark Streaming不像普通Spark 的应用程序,普通Spark程序运行完成后,中间数据会随着SparkContext的关闭而被销毁,而Spark Streaming一直在运行,不断计算,每一秒中在不断运行都会产生大量的中间数据,所以需要对对象及元数据需要定期清理。每个batch duration运行时不断触发job后需要清理rdd和元数据。下面我们就结合源码详细解析一下Spark Streaming程序的数据清理机制。

一、数据清理总览

Spark Streaming 运行过程中,随着时间不断产生Job,当job运行结束后,需要清理相应的数据(RDD,元数据信息,Checkpoint数据),Job由JobGenerator定时产生,数据的清理也是有JobGenerator负责。

JobGenerator负责数据清理控制的代码位于一个消息循环体eventLoop中:

  1. eventLoop = new EventLoop[JobGeneratorEvent]("JobGenerator") {
  2. override protected def onReceive(event: JobGeneratorEvent): Unit = processEvent(event)
  3. override protected def onError(e: Throwable): Unit = {
  4. jobScheduler.reportError("Error in job generator", e)
  5. }
  6. }
  7. eventLoop.start()

其中的核心逻辑位于processEvent(event)函数中:

  1. /** Processes all events */
  2. private def processEvent(event: JobGeneratorEvent) {
  3. logDebug("Got event " + event)
  4. event match {
  5. case GenerateJobs(time) => generateJobs(time)
  6. case ClearMetadata(time) => clearMetadata(time)
  7. case DoCheckpoint(time, clearCheckpointDataLater) =>
  8. doCheckpoint(time, clearCheckpointDataLater)
  9. case ClearCheckpointData(time) => clearCheckpointData(time)
  10. }
  11. }

可以看到当JobGenerator收到ClearMetadata(time) 和 ClearCheckpointData(time)是会进行相应的数据清理,其中 clearMetadata(time)会清理RDD数据和一些元数据信息, ClearCheckpointData(time)会清理Checkpoint数据。

二、数据清理过程详解
    2.1 ClearMetaData 过程详解
首先看一下clearMetaData函数的处理逻辑:
  1. /** Clear DStream metadata for the given `time`. */
  2. private def clearMetadata(time: Time) {
  3. ssc.graph.clearMetadata(time)
  4. // If checkpointing is enabled, then checkpoint,
  5. // else mark batch to be fully processed
  6. if (shouldCheckpoint) {
  7. eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = true))
  8. } else {
  9. // If checkpointing is not enabled, then delete metadata information about
  10. // received blocks (block data not saved in any case). Otherwise, wait for
  11. // checkpointing of this batch to complete.
  12. val maxRememberDuration = graph.getMaxInputStreamRememberDuration()
  13. jobScheduler.receiverTracker.cleanupOldBlocksAndBatches(time - maxRememberDuration)
  14. jobScheduler.inputInfoTracker.cleanup(time - maxRememberDuration)
  15. markBatchFullyProcessed(time)
  16. }
  17. }

首先调用了DStreamGraph的clearMetadata方法:

  1. def clearMetadata(time: Time) {
  2. logDebug("Clearing metadata for time " + time)
  3. this.synchronized {
  4. outputStreams.foreach(_.clearMetadata(time))
  5. }
  6. logDebug("Cleared old metadata for time " + time)
  7. }

这里调用了所有OutputDStream (关于DStream 的分类请参考http://blog.csdn.net/zhouzx2010/article/details/51460790)的clearMetadata方法

  1. private[streaming] def clearMetadata(time: Time) {
  2. val unpersistData = ssc.conf.getBoolean("spark.streaming.unpersist", true)
  3. //获取需要清理的RDD
  4. val oldRDDs = generatedRDDs.filter(_._1 <= (time - rememberDuration))
  5. logDebug("Clearing references to old RDDs: [" +
  6. oldRDDs.map(x => s"${x._1} -> ${x._2.id}").mkString(", ") + "]")

  7. //将要清除的RDD从generatedRDDs 中清除
  8. generatedRDDs --= oldRDDs.keys
  9. if (unpersistData) {
  10. logDebug(s"Unpersisting old RDDs: ${oldRDDs.values.map(_.id).mkString(", ")}")
  11. oldRDDs.values.foreach { rdd =>
  12.     //将RDD 从persistence列表中移除
  13. rdd.unpersist(false)
  14. // Explicitly remove blocks of BlockRDD
  15. rdd match {
  16. case b: BlockRDD[_] =>
  17. logInfo(s"Removing blocks of RDD $b of time $time")
  18. //移除RDD的block 数据
  19. b.removeBlocks()
  20. case _ =>
  21. }
  22. }
  23. }
  24. logDebug(s"Cleared ${oldRDDs.size} RDDs that were older than " +
  25. s"${time - rememberDuration}: ${oldRDDs.keys.mkString(", ")}")
  26. //清除依赖的DStream
  27. dependencies.foreach(_.clearMetadata(time))
  28. }

关键的清理逻辑在代码中做了详细注释,首先清理DStream对应的RDD的元数据信息,然后清理RDD的数据,最后对DStream所依赖的DStream进行清理。

回到JobGenerator的clearMetadata函数:

  1. if (shouldCheckpoint) {
  2. eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = true))
  3. } else {
  4. // If checkpointing is not enabled, then delete metadata information about
  5. // received blocks (block data not saved in any case). Otherwise, wait for
  6. // checkpointing of this batch to complete.
  7. val maxRememberDuration = graph.getMaxInputStreamRememberDuration()
  8. jobScheduler.receiverTracker.cleanupOldBlocksAndBatches(time - maxRememberDuration)
  9. jobScheduler.inputInfoTracker.cleanup(time - maxRememberDuration)
  10. markBatchFullyProcessed(time)
  11. }

调用了ReceiverTracker的 cleanupOldBlocksAndBatches方法,最后调用了clearupOldBatches方法:

  1. def cleanupOldBatches(cleanupThreshTime: Time, waitForCompletion: Boolean): Unit = synchronized {
  2. require(cleanupThreshTime.milliseconds < clock.getTimeMillis())
  3. val timesToCleanup = timeToAllocatedBlocks.keys.filter { _ < cleanupThreshTime }.toSeq
  4. logInfo(s"Deleting batches: ${timesToCleanup.mkString(" ")}")
  5. if (writeToLog(BatchCleanupEvent(timesToCleanup))) {
  6. //将要删除的Batch数据清除
  7. timeToAllocatedBlocks --= timesToCleanup
  8. //清理WAL日志
  9. writeAheadLogOption.foreach(_.clean(cleanupThreshTime.milliseconds, waitForCompletion))
  10. } else {
  11. logWarning("Failed to acknowledge batch clean up in the Write Ahead Log.")
  12. }
  13. }

可以看到ReceiverTracker的clearupOldBatches方法清理了Receiver数据,也就是Batch数据和WAL日志数据。
最后对InputInfoTracker信息进行清理:
  1. def cleanup(batchThreshTime: Time): Unit = synchronized {
  2. val timesToCleanup = batchTimeToInputInfos.keys.filter(_ < batchThreshTime)
  3. logInfo(s"remove old batch metadata: ${timesToCleanup.mkString(" ")}")
  4. batchTimeToInputInfos --= timesToCleanup
  5. }
这简单的清除了batchTimeToInputInfos 的输入信息。

2.2 ClearCheckPoint 过程详解
看一下clearCheckpointData的处理逻辑:
  1. /** Clear DStream checkpoint data for the given `time`. */
  2. private def clearCheckpointData(time: Time) {
  3. ssc.graph.clearCheckpointData(time)
  4. // All the checkpoint information about which batches have been processed, etc have
  5. // been saved to checkpoints, so its safe to delete block metadata and data WAL files
  6. val maxRememberDuration = graph.getMaxInputStreamRememberDuration()
  7. jobScheduler.receiverTracker.cleanupOldBlocksAndBatches(time - maxRememberDuration)
  8. jobScheduler.inputInfoTracker.cleanup(time - maxRememberDuration)
  9. markBatchFullyProcessed(time)
  10. }

后面的ReceiverTraker和InputInforTracker的清理逻辑和ClearMetaData的相同,这分析DStreamGraph的clearCheckpointData方法:

  1. def clearCheckpointData(time: Time) {
  2. logInfo("Clearing checkpoint data for time " + time)
  3. this.synchronized {
  4. outputStreams.foreach(_.clearCheckpointData(time))
  5. }
  6. logInfo("Cleared checkpoint data for time " + time)
  7. }

同样的调用了DStreamGraph中所有OutputDStream的clearCheckPiontData 方法:

  1. private[streaming] def clearCheckpointData(time: Time) {
  2. logDebug("Clearing checkpoint data")
  3. checkpointData.cleanup(time)
  4. dependencies.foreach(_.clearCheckpointData(time))
  5. logDebug("Cleared checkpoint data")
  6. }

这里的核心逻辑在checkpointData.cleanup(time)方法,这里的CheckpointData 是 DStreamCheckpointData对象, DStreamCheckpointData的clearup方法如下:
  1. def cleanup(time: Time) {
  2. // 获取需要清理的Checkpoint 文件 时间
  3. timeToOldestCheckpointFileTime.remove(time) match {
  4. case Some(lastCheckpointFileTime) =>
  5. //获取需要删除的文件
  6. val filesToDelete = timeToCheckpointFile.filter(_._1 < lastCheckpointFileTime)
  7. logDebug("Files to delete:\n" + filesToDelete.mkString(","))
  8. filesToDelete.foreach {
  9. case (time, file) =>
  10. try {
  11. val path = new Path(file)
  12. if (fileSystem == null) {
  13. fileSystem = path.getFileSystem(dstream.ssc.sparkContext.hadoopConfiguration)
  14. }
  15. //  删除文件   
  16. fileSystem.delete(path, true)
  17. timeToCheckpointFile -= time
  18. logInfo("Deleted checkpoint file '" + file + "' for time " + time)
  19. } catch {
  20. case e: Exception =>
  21. logWarning("Error deleting old checkpoint file '" + file + "' for time " + time, e)
  22. fileSystem = null
  23. }
  24. }
  25. case None =>
  26. logDebug("Nothing to delete")
  27. }
  28. }

可以看到checkpoint的清理,就是删除了指定时间以前的checkpoint文件。

三、数据清理的触发
    3.1 ClearMetaData 过程的触发
JobGenerator 生成job后,交给JobHandler执行, JobHandler的run方法中,会在job执行完后给JobScheduler 发送JobCompleted消息:
  1. _eventLoop = eventLoop
  2. if (_eventLoop != null) {
  3. _eventLoop.post(JobCompleted(job, clock.getTimeMillis()))
  4. }
JobScheduler 收到JobCompleted 消息调用 handleJobCompletion 方法,源码如下:

  1. private def processEvent(event: JobSchedulerEvent) {
  2. try {
  3. event match {
  4. case JobStarted(job, startTime) => handleJobStart(job, startTime)
  5. case JobCompleted(job, completedTime) => handleJobCompletion(job, completedTime)
  6. case ErrorReported(m, e) => handleError(m, e)
  7. }
  8. } catch {
  9. case e: Throwable =>
  10. reportError("Error in job scheduler", e)
  11. }
  12. }

在 JobScheduler 的handleJobCompletion方法中会调用JobGenerator的onBatchCompletion方法,我们看一下JobGenerator的 onBatchCompletion 方法的源码:

  1. def onBatchCompletion(time: Time) {
  2. eventLoop.post(ClearMetadata(time))
  3. }

可以看到JobGenerator的onBatchCompletion方法给自己发送了ClearMetadata消息从而触发了ClearMetaData操作。

3.2 ClearCheckPoint 过程的触发
    清理CheckPoint数据发生在CheckPoint完成之后,我们先看一下CheckPointHandler的run方法:

  1. // All done, print success
  2. val finishTime = System.currentTimeMillis()
  3. logInfo("Checkpoint for time " + checkpointTime + " saved to file '" + checkpointFile +
  4. "', took " + bytes.length + " bytes and " + (finishTime - startTime) + " ms")
  5. //调用JobGenerator的方法进行checkpoint数据清理
  6. jobGenerator.onCheckpointCompletion(checkpointTime, clearCheckpointDataLater)
  7. return

可以看到在checkpoint完成后,会调用JobGenerator的onCheckpointCompletion方法进行checkpoint数据清理,我查看JobGenerator的onCheckpointCompletion方法源码:

  1. def onCheckpointCompletion(time: Time, clearCheckpointDataLater: Boolean) {
  2. if (clearCheckpointDataLater) {
  3. eventLoop.post(ClearCheckpointData(time))
  4. }
  5. }

可以看到JobGenerator的onCheckpointCompletion方法中首先对传进来的 clearCheckpointDataLater 参数进行判断,如果该参数为true,就会给JobGenerator的eventLoop循环体发送ClearCheckpointData消息,从而触发clearCheckpointData 方法的调用,进行Checkpoint数据的清理。
什么时候该参数会true呢?
我们回到JobGenerator的 ClearMetadata 方法:

  1. private def clearMetadata(time: Time) {
  2. ssc.graph.clearMetadata(time)
  3. if (shouldCheckpoint) {
  4. //发送DoCheckpoint消息,并进行相应的Checkpoint数据清理
  5. eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = true))
  6. } else {
  7. val maxRememberDuration = graph.getMaxInputStreamRememberDuration()
  8. jobScheduler.receiverTracker.cleanupOldBlocksAndBatches(time - maxRememberDuration)
  9. jobScheduler.inputInfoTracker.cleanup(time - maxRememberDuration)
  10. markBatchFullyProcessed(time)
  11. }
  12. }

可以看到在clearMetadata方法中,发送了DoCheckpoint消息,其中参数 clearCheckpointDataLater 为ture。Generator的eventLoop收到该消息后调用 doCheckpoint 方法:

  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)
  5. checkpointWriter.write(new Checkpoint(ssc, time), clearCheckpointDataLater)
  6. }
  7. }

这里关键一步:调用了CheckpointWriter的write方法,注意此时参数 clearCheckpointDataLater 为true。我们进入该方法:

  1. def write(checkpoint: Checkpoint, clearCheckpointDataLater: Boolean) {
  2. try {
  3. val bytes = Checkpoint.serialize(checkpoint, conf)
  4. //将参数clearCheckpointDataLater传入CheckpoitWriteHandler
  5. executor.execute(new CheckpointWriteHandler(
  6. checkpoint.checkpointTime, bytes, clearCheckpointDataLater))
  7. logInfo("Submitted checkpoint of time " + checkpoint.checkpointTime + " writer queue")
  8. } catch {
  9. case rej: RejectedExecutionException =>
  10. logError("Could not submit checkpoint task to the thread pool executor", rej)
  11. }
  12. }

可以看到此时参数 clearCheckpointDataLater 传入CheckpointWriteHandler 。这样Checkpoint完成之后就会发送ClearCheckpointData消息给JobGenerator进行Checkpoint数据的清理。

原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/)







16.Spark Streaming源码解读之数据清理机制解析的更多相关文章

  1. Spark Streaming源码解读之数据清理内幕彻底解密

    本期内容 : Spark Streaming数据清理原理和现象 Spark Streaming数据清理代码解析 Spark Streaming一直在运行的,在计算的过程中会不断的产生RDD ,如每秒钟 ...

  2. Spark Streaming源码解读之JobScheduler内幕实现和深度思考

    本期内容 : JobScheduler内幕实现 JobScheduler深度思考 JobScheduler 是整个Spark Streaming调度的核心,需要设置多线程,一条用于接收数据不断的循环, ...

  3. Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

    本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...

  4. 15、Spark Streaming源码解读之No Receivers彻底思考

    在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Appr ...

  5. 第12课:Spark Streaming源码解读之Executor容错安全性

    一.Spark Streaming 数据安全性的考虑: Spark Streaming不断的接收数据,并且不断的产生Job,不断的提交Job给集群运行.所以这就涉及到一个非常重要的问题数据安全性. S ...

  6. Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

    本期内容 : 数据接收架构设计模式 数据接收源码彻底研究 一.Spark Streaming数据接收设计模式   Spark Streaming接收数据也相似MVC架构: 1. Mode相当于Rece ...

  7. Spark Streaming源码解读之Receiver生成全生命周期彻底研究和思考

    本期内容 : Receiver启动的方式设想 Receiver启动源码彻底分析 多个输入源输入启动,Receiver启动失败,只要我们的集群存在就希望Receiver启动成功,运行过程中基于每个Tea ...

  8. Spark Streaming源码解读之Job动态生成和深度思考

    本期内容 : Spark Streaming Job生成深度思考 Spark Streaming Job生成源码解析 Spark Core中的Job就是一个运行的作业,就是具体做的某一件事,这里的JO ...

  9. 11.Spark Streaming源码解读之Driver中的ReceiverTracker架构设计以及具体实现彻底研究

    上篇文章详细解析了Receiver不断接收数据的过程,在Receiver接收数据的过程中会将数据的元信息发送给ReceiverTracker:   本文将详细解析ReceiverTracker的的架构 ...

随机推荐

  1. STL源码分析-function

    http://note.youdao.com/noteshare?id=269e16541b43095eaf97636d4e046b1d

  2. Qt ------ Thread Affinity (QObject::moveToThread: Cannot move objects with a parent)

    简单的说,每个QObject的对象,都和某个创建对象所在的线程关联,如果把对象通过 moveToThread 移动到其他线程,这个对象不能有父对象,否则会出现 QObject::moveToThrea ...

  3. Ant Design Upload 组件上传文件到云服务器 - 七牛云、腾讯云和阿里云的分别实现

    在前端项目中经常遇到上传文件的需求,ant design 作为 react 的前端框架,提供的 upload 组件为上传文件提供了很大的方便,官方提供的各种形式的上传基本上可以覆盖大多数的场景,但是对 ...

  4. 【POJ】2142 The Balance 数论(扩展欧几里得算法)

    [题意]给定a,b,c,在天平左边放置若干重量a的砝码,在天平右边放置若干重量b的砝码,使得天平两端砝码差为c.设放置x个A砝码和y个B砝码,求x+y的最小值. [算法]数论(扩展欧几里德算法) [题 ...

  5. How to reset XiaoMi bluetooth headphone Youth edition.

    To reset the speaker 1. Long press the phone call button to shut off the speaker 2. Connect the char ...

  6. Spring整合Quartz分布式调度

    前言 为了保证应用的高可用和高并发性,一般都会部署多个节点:对于定时任务,如果每个节点都执行自己的定时任务,一方面耗费了系统资源,另一方面有些任务多次执行,可能引发应用逻辑问题,所以需要一个分布式的调 ...

  7. 【leetcode 简单】第三十七题 相交链表

    编写一个程序,找到两个单链表相交的起始节点. 例如,下面的两个链表: A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3 在节点 c1 开始相交. 注意: 如果两个 ...

  8. NYOJ 257 郁闷的C小加(一) (字符串处理)

    题目链接 描述 我们熟悉的表达式如a+b.a+b(c+d)等都属于中缀表达式.中缀表达式就是(对于双目运算符来说)操作符在两个操作数中间:num1 operand num2.同理,后缀表达式就是操作符 ...

  9. Oracle笔记之用户管理

    1. 创建用户 创建用户使用create user语句,需要DBA权限: CREATE USER tom IDENTIFIED BY mot; 2. 更改用户密码 修改别人的密码需要DBA权限,或者a ...

  10. Tensorflow中使用TFRecords高效读取数据--结合Attention-over-Attention Neural Network for Reading Comprehension

    原文链接:https://arxiv.org/pdf/1607.04423.pdf 本片论文主要讲了Attention Model在完形填空类的阅读理解上的应用. 转载:https://blog.cs ...