Spark Streaming的恢复机制——Checkpoint

前言:
一个Streaming Application 往往需要7*24 不间断的跑,所以需要有自动恢复失败前的状态(机器冗机,系统挂掉,jvm crash等),为了让上述成为可能,Spark Streaming需要checkpoint 足够多信息至一个具有容错设计的存储系统(HDFS数据备份)才能让Application从失败中恢复过来

Spark Streaming 会 checkpoint 两种类型的数据

  • Metadata(元数据) checkpointing - 保存定义了Streaming 计算逻辑至类似HDFS的支持容错的存储系统,用来恢复driver,
    原数据保存:
    1.配置-用于创建该Streaming application 的所有配置
    2.DStream 操作 - DStream 一些列的操作
    3.未完成的batches - 那些提交了job 但尚未执行或者未完成的batches

  • Data checkpointing - 保存已生产的RDDs至可靠的存储,这在某些stateful 转换中是有需要的,在这种转换中,生成RDD需要依赖前面的batches,会导致依赖链随着时间而变成,为了避免这种没有尽头的边长,要定期将中间生成的RDDs保存到可靠存储来切断依赖链

总之,metadata checkpointing 主要用来恢复 driver;而 RDD数据的 checkpointing 对于stateful 转换操作是必要的。

什么时候需要启用 checkpoint?

什么时候该启用 checkpoint 呢?满足以下任一条件:

  • 使用了 stateful 转换 - 如果 application 中使用了updateStateByKey或reduceByKeyAndWindow等 stateful 操作,必须提供 checkpoint 目录来允许定时的 RDD checkpoint (shuffle操作)
  • 希望能从意外中恢复 driver

如果 streaming app 没有 stateful 操作,也允许 driver 挂掉后再次重启的进度丢失,就没有启用 checkpoint的必要了。

如何使用 checkpoint?

启用 checkpoint,需要设置一个支持容错 的、可靠的文件系统(如 HDFS、s3 等)目录来保存 checkpoint 数据。通过调用 streamingContext.checkpoint(checkpointDirectory) 来完成。另外,如果你想让你的 application 能从 driver 失败中恢复,你的 application 要满足:

  • 若 application 为首次重启,将创建一个新的 StreamContext 实例
  • 如果 application 是从失败中重启,将会从 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里面构建StreamingContext
def functionToCreateContext(): StreamingContext = {
  val ssc = new StreamingContext(...)   // new context
  val rdds = ssc.socketTextStream(...) // create DStreams
  ...
  ssc.checkpoint("/spark/kmd/checkpoint")   // 设置在HDFS上的checkpoint目录
  //设置通过间隔时间,定时持久checkpoint到hdfs上
  rdds.checkpoint(Seconds(batchDuration*5))

  rdds.foreachRDD(rdd=>{
  //可以针对rdd每次调用checkpoint
  //注意上面设置了,定时持久checkpoint下面这个地方可以不用写
  rdd.checkpoint()

  }
  )
  //返回ssc
  ssc
}

def main(args:Array){
// 创建context
val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)
// 启动流计算
context.start()
context.awaitTermination()
}

如果 checkpointDirectory 存在,那么 context 将导入 checkpoint 数据。如果目录不存在,函数 functionToCreateContext 将被调用并创建新的 context

ps:
除调用 getOrCreate 外,还需要你的集群模式支持 driver 挂掉之后重启之。例如,在 yarn 模式下,driver 是运行在 ApplicationMaster 中,若 ApplicationMaster 挂掉,yarn 会自动在另一个节点上启动一个新的 ApplicationMaster。

需要注意的是,随着 streaming application 的持续运行,checkpoint 数据占用的存储空间会不断变大。因此,需要小心设置checkpoint 的时间间隔。设置得越小,checkpoint 次数会越多,占用空间会越大;如果设置越大,会导致恢复时丢失的数据和进度越多。一般推荐设置为 batch duration 的5~10倍。

导出 checkpoint 数据

上文提到,checkpoint 数据会定时导出到可靠的存储系统,那么
1.在什么时机进行 checkpoint
2.checkpoint 的形式是怎么样的

在什么时机进行 checkpoint
在 Spark Streaming 中,JobGenerator 用于生成每个 batch 对应的 jobs,它有一个定时器,定时器的周期即初始化 StreamingContext 时设置的 batchDuration。这个周期一到,JobGenerator 将调用generateJobs方法来生成并提交 jobs,这之后调用 doCheckpoint 方法来进行 checkpoint。doCheckpoint 方法中,会判断当前时间与 streaming application start 的时间之差是否是 checkpoint duration 的倍数,只有在是的情况下才进行 checkpoint。

checkpoint 的形式
最终 checkpoint 的形式是将类 Checkpoint的实例序列化后写入外部存储,值得一提的是,有专门的一条线程来做将序列化后的 checkpoint 写入外部存储。类 Checkpoint 包含以下数据这里写图片描述

除了 Checkpoint 类,还有 CheckpointWriter 类用来导出 checkpoint,CheckpointReader 用来导入 checkpoint

Checkpoint 的局限

Spark Streaming 的 checkpoint 机制看起来很美好,却有一个硬伤。上文提到最终刷到外部存储的是类 Checkpoint 对象序列化后的数据。那么在 Spark Streaming application 重新编译后,再去反序列化 checkpoint 数据就会失败。这个时候就必须新建 StreamingContext。

坑一,会报错:

xxxx classs ClassNotFoundException

问题就出在checkpoint上,因为checkpoint的元数据会记录jar的序列化的二进制文件,因为你改动过代码,然后重新编译,新的序列化jar文件,在checkpoint的记录中并不存在,所以就导致了上述错误,如何解决:

解决方法:
删除checkpoint开头的的文件即可,不影响数据本身的checkpoint

hadoop fs -rm /spark/kmd/check_point/checkpoint*

坑二:
处理的逻辑必须写在functionToCreateContext函数中,你要是直接写在main方法中,在首次启动后,kill关闭,再启动就会报错

关闭命令

yarn application -kill application_1482996264071_34284

再次启动后报错信息:

has not been initialized when recovery from checkpoint

解决方案:将逻辑写在函数中,不要写main方法中,

针对这种情况,在我们结合 Spark Streaming + kafka 的应用中,我们自行维护了消费的 offsets,这样一来及时重新编译 application,还是可以从需要的 offsets 来消费数据,这里只是举个例子,不详细展开了。

猜你喜欢

转载自blog.csdn.net/yjgithub/article/details/78792616