《Spark Streaming checkpoint 实现状态恢复》

前一篇翻译了官网关于缓存和检查点机制的介绍,并没有写代码实现。这里改造下有状态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函数里,这里除了启动程序,别的流程都在里面。

这里梳理一下程序执行的整个流程:

  1. 启动main函数,StreamingContext调用其getCreate方法;
  2. 如果checkpointPath下没有数据,则调用functionToCreateContext方法创建一个新的StreamingContext执行该函数定义的逻辑;
  3. 如果checkpointPath有数据,则根据这些数据恢复失败前的计算状态。
  4. ssc.start()启动计算。
  5. 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

猜你喜欢

转载自blog.csdn.net/Mathieu66/article/details/85107607