前一篇翻译了官网关于缓存和检查点机制的介绍,并没有写代码实现。这里改造下有状态wordCount示例,简要介绍下检查点机制的启用,以及如何在driver失败时从检查点恢复。
检查点的启动非常简单,只需要配置下checkpoint路径即可:StreamingContext.checkpoint(checkpointDirectory),而若想从检查点恢复上次计算,则需要重写部分代码,实现下述功能:
- 当程序第一次启动时,会创建一个新的StreamingContext ,设置所有的stream 并调用 start()
- 当程序从失败中恢复时,会根据检查点存储的信息重新创建StreamingContext
下面我们看代码:
package com.ccclubs.streaming
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.kafka010.KafkaUtils
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author xianghu.wang
* @date: 2018-12-18 15:47
* @des: 可容错的有状态wordcount示例
*/
object StreamingWordCount {
def main(args: Array[String]): Unit = {
// 创建StreamingContext
/* StreamingContext.getOrCreate方法会优先查看检查点路径下是否有之前保存的数据
如果有,则根据已经保存的数据恢复失败前的计算状态;如果没有,则认为是第一次启动,调用
functionToCreateContext方法创建一个新的StreamingContext开始任务*/
val ssc = StreamingContext.getOrCreate("./checkpoint", functionToCreateContext _)
// 开始
ssc.start()
ssc.awaitTermination()
}
/**
*
* @param newValues 新值序列,其类型对应键值对中的值类型(这里是Int)
* @param oldCount 之前统计的值
* @return
*/
def updateFunction(newValues: Seq[Int], oldCount: Option[Int]): Option[Int] = {
val newCount = newValues.sum
val previousCount = oldCount.getOrElse(0)
Some(newCount + previousCount)
}
/**
* StreamingContext构建函数
*
* @return
*/
def functionToCreateContext(): StreamingContext = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreamingDemo")
val ssc = new StreamingContext(sparkConf, Seconds(1))
// 配置检查点目录
ssc.checkpoint("./checkpoint")
// kafka参数
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "zc01:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "SparkStreamingDemo",
"auto.offset.reset" -> "latest"
)
// kafka主题
val topics = Array("topicA")
// 从kafka创建DStream
val stream = KafkaUtils.createDirectStream[String, String](
ssc,
PreferConsistent,
Subscribe[String, String](topics, kafkaParams)
)
// stream中的每一条记录都是一个ConsumerRecord,
// public ConsumerRecord(topic: String, partition: Int, offset: Long, key: K, value: V)
val kvs = stream.map(record => (record.value, 1))
val count = kvs.updateStateByKey[Int](updateFunction _)
// 打印在控制台
count.print()
ssc
}
}
与常规代码不同的地方是,业务逻辑全写在了 functionToCreateContext函数里,这里除了启动程序,别的流程都在里面。
这里梳理一下程序执行的整个流程:
- 启动main函数,StreamingContext调用其getCreate方法;
- 如果checkpointPath下没有数据,则调用functionToCreateContext方法创建一个新的StreamingContext执行该函数定义的逻辑;
- 如果checkpointPath有数据,则根据这些数据恢复失败前的计算状态。
- ssc.start()启动计算。
- ssc.awaitTermination()等待计算结束。
下面我们来运行下程序:
1)启动kafka生产者:
2)启动Streaming程序:
3)杀死kafka生产者和streaming程序(生产者一并杀死,以验证恢复的数据是否和失败前最后一刻的状态相同):
4)启动 Streaming程序
可见,杀死streaming程序后,从检查点恢复的状态和杀死之前的状态完全相同,从检查点恢复计算状态成功。
注:转载请注明出处
参考:http://spark.apache.org/docs/latest/streaming-programming-guide.html#how-to-configure-checkpointing