Checkpoint
我们必须记录一些信息以方便恢复现场,在Spark Streaming中使用checkpoint实现恢复操作。
Spark Streaming中有两种不同对象的checkpont操作。
元数据(Metadata) checkpointing
保存Streaming中定义流计算的相关信息到可信赖的文件系统如HDFS,这种方式用于运行Driver的节点失败之后的恢复。
元数据包括:
- 配置---创建context时的配置
- DStream的操作---定义流应用程序的DStream操作集
- 未完成的批次---记录已经在队列中但是还没有完成的批次
数据(Data) checkpointing
保存生成的RDD到可信赖的存储中,有一种场景是一些有状态的transformations是跨多个批次的数据组合,生成的RDD依赖上个批次的RDD,导致RDD的依赖链随着程序运行越来越长,当程序失败后,因为依赖链过长,恢复需要相当长的时间,如果定期checkponit就可以切断依赖链以减少恢复时间。
注意:切断RDD之间的依赖链只是Data checkpointing顺带解决的问题,而不是设计Data checkpointing的目的,Data checkpointing的目的和作用还是保存RDD到可信赖的存储。
总的来说,元数据checkpoint主要是为了driver失败后的恢复,然而,如果使用上面提及的stateful transformations,RDD的checkpoint也是必要的。
checkpoint应用场景
checkpoint在以下场景中是必要的:
- stateful transformations:在使用updateStateByKey, reduceByKeyAndWindow这类算子之后必须使用checkpoint
- 从运行应用程序的驱动程序的故障中恢复——元数据检查点用于恢复进度信息。
checkpoint的使用
对于上面提及的Stateful checkpoint(第2类),可以直接使用streamingContext.checkpoint(checkpointDirectory)即可
对于方便driver恢复的checkpoint(第1类),在应用程序中必须满足以下行为:
- 当应用程序一启动,将创建StreamingContext,配置流信息然后start()
- 当应用程序失败之后的重新启动,将会从checkpoint目录中的checkpoint数据中恢复StreamingContext。
上面的两种行为可以用StreamingContext.getOrCreate完成。
// Function to create and setup a new StreamingContext def functionToCreateContext(): StreamingContext = { val ssc = new StreamingContext(...) // new context val lines = ssc.socketTextStream(...) // create DStreams ... ssc.checkpoint(checkpointDirectory) // set checkpoint directory ssc } // Get StreamingContext from checkpoint data or create a new one val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _) // Do additional setup on context that needs to be done,// irrespective of whether it is being started or restarted context. ... // Start the context context.start() context.awaitTermination()
当checkpoint目录不存在时,将重新创建context。
累加器与广播变量
累加器与广播变量不能直接从checkpoint中恢复,如果想使用累加器与广播变量,可以借助单例对象,创建一个懒加载的单例实例对象保存累加器与广播变量,当driver由于失败重新启动时,将会重新实例化这些懒加载的单例对象。
object WordBlacklist { @volatile private var instance: Broadcast[Seq[String]] = null def getInstance(sc: SparkContext): Broadcast[Seq[String]] = { if (instance == null) { synchronized { if (instance == null) { val wordBlacklist = Seq("a", "b", "c") instance = sc.broadcast(wordBlacklist) } } } instance }} object DroppedWordsCounter { @volatile private var instance: LongAccumulator = null def getInstance(sc: SparkContext): LongAccumulator = { if (instance == null) { synchronized { if (instance == null) { instance = sc.longAccumulator("WordsInBlacklistCounter") } } } instance }} wordCounts.foreachRDD { (rdd: RDD[(String, Int)], time: Time) => // Get or register the blacklist Broadcast val blacklist = WordBlacklist.getInstance(rdd.sparkContext) // Get or register the droppedWordsCounter Accumulator val droppedWordsCounter = DroppedWordsCounter.getInstance(rdd.sparkContext) // Use blacklist to drop words and use droppedWordsCounter to count them val counts = rdd.filter { case (word, count) => if (blacklist.value.contains(word)) { droppedWordsCounter.add(count) false } else { true } }.collect().mkString("[", ", ", "]") val output = "Counts at time " + time + " " + counts})