英文原文
2019-03
checkpoint (检查点)用于恢复与处理与应用逻辑无关的故障,比如 system failures, JVM crashes。
将足够的信息存储在可靠的存储中,使程序能够从故障中恢复。
一共有两种类型的数据需要被存储为检查点:
- 元数据检查点 - 存储streaming计算的定义到可靠的存储中,比如 HDFS。用于从故障中恢复运行 driver 的节点。元数据包括:
- 配置 - 创建streaming应用的配置。
- DStream 操作 - 定义 streaming 应用的 DStream 操作集
- 未完成的批次 - 在队列中但是还没有完成的 jobs。
- 数据检查点 - 存储生成的数据到可靠的存储中。这对于联合了多个批次的有状态的转换来说是必要的。在这种转换中,生成的 RDD 是依赖于之前批次的 RDD的,而导致依赖链条的长度随着时间的增加而增加。为了避免恢复时间的增加,在RDD之间的转换状态周期性的存储到可靠的存储中。
一句话, 元数据检查点是必须的,数据检查点对于带有状态转换的应用是必要的,即使是一些基本操作。
何时使用 checkpoint
如果应用存在下面的情况,则必须使用 checkpoint。
- 使用了有状态的转换操作 - 无论在应用中是使用了
updateStateByKey
还是reduceByKeyAndWindow
,则必须提供 checkpoint 的目录以可以周期性的存储 RDD 检查点。 - 要从运行应用程序的 driver 的故障中恢复 - 元数据检查点用于恢复进程信息。
请注意,可以在不启用检查点的情况下运行没有上述有状态转换的简单流应用程序。 在这种情况下,驱动程序故障的恢复也将是部分的(某些已接收但未处理的数据可能会丢失)。 这通常是可以接受的,并且有许多以这种方式运行Spark Streaming应用程序。 预计对非Hadoop环境的支持将在未来得到改善。
配置 checkpoint
通过设置可靠存储的目录(比如HDFS)来激活检查点。使用 streamingContext.checkpoint(checkpointDirectory)
,你可以被允许使用上述的有状态转换操作。另外,如果你想要从 dirver 的故障中恢复,你需要使用下面的指示重写spark streaming 应用:
- 当程序第一次启动的时候,程序会创建一个新的 StreamingContext,设置所有 streams 并且启动。
- 如果程序在发生故障后重启,程序会从检查点的目录中的 checkpoint 中重新创建一个 StreamingContext。
// 创建并设置一个新的 StreamingContext 的方法。
def functionToCreateContext(): StreamingContext = {
val ssc = new StreamingContext(...) // new context
val lines = ssc.socketTextStream(...) // create DStreams
...
ssc.checkpoint(checkpointDirectory) // set checkpoint directory
ssc
}
// 直接创建一个 StreamingContext 或者根据检查点的数据创建。
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)
// 在 context 上做必要的设置,不管他是直接启动的还是重启的。
context. ...
// Start the context
context.start()
context.awaitTermination()
如果 检查点目录
存在,则会根据检查点数据重建 StreamingContext。如果不存在,就执行 functionToCreateContext
创建一个新的 StreamingContext 并设置 DStream。另外有一个官方例子RecoverableNetworkWordCount
注意:检查点会增加存储数据到外部存储的开销。会增加streaming处理批次的时间,所以,存储检查点的间隔要小心设置,过于频繁会显著降低应用的吞吐率。相反,检查点间隔过长会导致要任务数过多(包含过多要存储的数据与要恢复的数据)也会到来较大的危害。对于有状态的转换操作,默认的检查点间隔是批次间隔的几倍,至少 10 秒。可以使用 dstream.checkpoint(checkpointInterval)
来设置。一般设置 5-10秒比合适。
增量器、广播器与检查点
在 Spark Streaming 中,从检查点中不会恢复增量器与广播器,所以你在检查点的同时使用了增量器与广播器,可以为他们设置一个懒初始化的单例,这样他们就可以在 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
})
End!!