[译]Spark Streaming编程指南(四)

缓存/持久化

和RDD类似,DStream允许开发者将流数据持久化到内存。使用在DStream上使用persist()方法会自动持久化DStream中的每个EDD到内存中。这对于DStream需要计算多次的情况非常有用(如在相同数据上进行多个操作)。对于window-based操作(如reduceByWindowreduceByKeyAndWindow)和state-based操作(如updateStateByKey),会隐式地进行持久化,因此,通过window-based操作生成的DStream会自动持久化到内存,不需要开发者调用persist()

对于通过网络接收的输入数据流(如Kafka,Flume,socket等),默认持久化级别设置为在两个节点中保存副本,以便进行容错处理。

注意,不像RDD,DStream的偶人持久化级别会将数据序列化保存到内存中。在之后性能调优中会进一步讨论,更多关于持久化级别的信息参见Spark编程指南(二)

检查点

streaming应用程序必须7*24小时运行,因此必须对于应用程序逻辑无关的错误具有弹性(如系统错误,JVM崩溃等)。Spark Streaming需要将足够的检查点信息放到容错存储系统,以便之后从错误中恢复,有两种类型的数据需要设置检查点。

  • 元数据检查点-将定义streaming计算的信息存储到容错存储系统中,如HDFS。用于从运行streaming驱动程序的节点错误中恢复,元数据包括:

    • 配置-用于创建streaming应用程序的配置。
    • DStream操作-定义streaming应用程序的DStream操作集合。
    • 未完成的批次-批次的作业在队列中但是还没计算完成。
  • 数据检查点-保存生成的RDD到可靠的存储系统中,对于一些带状态的转换(跨多个批次合并数据)是必要的。在这样的转换中,生成的RDD要依赖之前批次的RDD,导致依赖链的长度会随着时间增长。为避免这种无限增长,带状态转换的中间RDD会周期性地在可靠存储系统中保存检查点(如HDFS)来切断依赖链。

总结来说,元数据检查点用于从驱动程序的错误中恢复,如果使用了带状态的转换,数据或RDD检查点是必要的。

何时启用检查点
以下几种情况必须为应用程序启用检查点:

  • 使用了带状态的转换-如果在应用程序中使用了updateStateByKeyreduceByKeyAndWindow,就必须提供检查点目录用于周期性地存储RDD检查点。
  • 从驱动程序的错误中恢复-检查点元数据用于恢复进度信息。

注意,没有使用带状态转换的简单streaming应用程序可以不启用检查点。在这种情况下,从驱动程序错误中恢复也是局部的(一些已经接收但是没有处理的数据可能会丢失)。这通常是可接受的,很多Spark Streaming应用程序用这种方式运行。对非Hadoop环境的支持会在未来进行改善。

如何配置检查点
启用检查点,需要设置一个容错可靠的文件系统(如HDFS,S3等)中的目录,用于存储检查点信息。使用streamingContext.checkpoint(checkpointDirectory)进行设置。这样就可以使用带状态的转换了。另外,如果想要让应用程序从驱动程序的错误中恢复,需要重写streaming应用程序包含以下行为。

  • 当程序第一次启动时,会创建一个新的StreamingContext,然后调用start()。
  • 当程序在错误后重新启动时,会从检查点数据中重新创建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()

如果checkpointDirectory存在,那么上下文会通过检查见数据重新创建。如果目录不存在(如第一次运行),那么函数functionToCreateContext会创建新的上下文和DStream。可以参考示例RecoverableNetworkWordCount。这个示例程序会像一个文件追加网络数据的单词计数。

除了使用getOrCreate之外,还需要保证驱动程序在出现错误时会自动重新启动。这只能通过应用程序的部署方式来完成。之后会进一步讨论。

注意,RDD的检查点会带来存储到可靠存储系统的成本,这会导致需要RDD检查点的批次处理时间增加。因此,检查点的时间间隔需要小心设置。对于比较小的批次,每个批次的检查点可能会明显减少操作的吞吐量。相反,检查点太少会导致任务规模的增加,带来不利影响。对于需要RDD检查点的带状态转换,默认时间间隔是批时间间隔的倍数,不少于10s。可通过使用dstream.checkpoint(checkpointInterval)进行设置。通常,检查点时间间隔为5-10个DStream时间间隔是比较好的。

累加器,广播变量和检查点

累加器广播变量不能从Spark Streaming的检查点中恢复。如果启用了检查点并且同时使用了累加器广播变量,必须为累加器广播变量创建懒实例化的单例,以便在驱动程序从失败中重新启动后可以重新实例化它们。下面是个示例。

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
})

完整代码参见source code

部署应用程序

这一节讨论部署Spark Streaming应用程序的步骤。

要求
要运行Spark Streaming应用程序,需要以下几点。

  • 有集群管理器的集群 - 这是任何Spark应用程序的通用要求,具体参见deployment guide
  • 打应用程序JAR包 - 必须将streaming应用程序编译成JAR包。如果使用spark-submit
    启动应用程序,那就不需要再JAR报中提供Spark和Spark Streaming。但是,如果使用高级源(如Kafka,Flume),那就必须将额外的库及其引用打到JAR包中。例如,使用KafkaUtils的应用程序必须将spark-streaming-kafka-0-8_2.11及其传递依赖打到JAR包中。
  • 为executor配置足够的内存 - 因为接收的数据必须存储在内存中,所以executor必须配置足够的内存。注意如果正在执行10分钟的window operations,系统必须在内存中保持至少10分钟的数据。应用程序的内存要求是根据其使用的操作决定的。
  • 配置检查点 - 如果streaming应用程序需要,则需要配置检查点目录,是Hadoop API兼容的容错存储目录(如HDFS,S3等),编写streaming应用程序需要使用检查点信息进行错误恢复。具体参见之前检查点相关内容。
  • 配置应用程序驱动自动重启 - 为了自动从驱动程序错误中恢复,部署streaming应用程序必须监控驱动进程并且如果出现错误要重启。不同的集群管理器有不同的工具实现这个机制。
    • Spark Standalone - Spark程序可以在Spark Standalone集群中运行(参见cluster deploy mode),这就是说,应用驱动程序在一个worker节点上运行。因此,Standalone集群管理器可用于监控驱动程序,并且在驱动程序因为non-zero退出代码或节点错误出错时进行重启。
    • YARN - Yarn支持自动重启应用程序的类似机制。请参见YARN文档。
    • Mesos - 在Mesos中Marathon用于实现这个机制。
  • 配置写ahead日志 - 从Spark 1.2开始,引入了写ahead日志以实现强大的容错保证,如果启用,所有从receiver接收到的数据都会写入ahead日志中存储在配置好的检查点目录中。避免了驱动程序恢复时数据丢失的问题,可以做到零数据丢失。可以通过设置spark.streaming.receiver.writeAheadLog.enabletrue来启用。但是,可能会以降低单个receiver的吞吐量为代价。可通过并行运行多个receiver来增加吞吐量。此外,当写ahead日志时,推荐将接收数据的副本关闭,因为日志已经存储到复制存储系统中了。可以通过设置输入流的存储等级为StorageLevel.MEMORY_AND_DISK_SER来实现。当使用S3(或者任何不支持flushing的文件系统)写ahead日志时,请家住启用spark.streaming.driver.writeAheadLog.closeFileAfterWritespark.streaming.receiver.writeAheadLog.closeFileAfterWrite。具体参见Spark Streaming Configuration。注意,在启用I/O加密时,Spark不会加密写入ahead日志中的数据。如果想要加密ahead日志中的数据,应该写入原生支持加密的文件系统。
  • 设置最大接收速率 - 如果集群资源对应streaming应用程序尽可能快递处理数据不够的话,receiver可以进行限速,设置最大接收速率限制records / sec。参见configuration parameters中的receiver参数spark.streaming.receiver.maxRate和Kafka参数spark.streaming.kafka.maxRatePerPartition。在Spark 1.5中,介绍了一个叫做backpressure的特性,消除设置此速率限制的必要性,Spark Streaming会自动计算速率限制,并在处理条件改变时动态适配。可以通过设置spark.streaming.backpressure.enabledtrue来启用backpressure。

升级应用程序代码
如果正在运行的Spark Streaming应用程序需要升级到新的应用程序代码,有两种可行的机制。

  • 升级后的Spark Streaming应用程序启用与现有应用程序并行运行。一旦新应用程序(与旧应用程序接收同样的数据)已经预热好并且运行良好即可将旧的应用程序停掉。注意,这可用于支持方到两个目的地(分别发给新旧应用程序)的数据源。
  • 现有应用程序正在优雅地关闭(参见StreamingContext.stop(...)
    JavaStreamingContext.stop(...)
    优雅关闭选项),确保已接收的数据在关机前已经完全处理好。然后升级的应用程序可以启动,将从之前应用程序关闭的同一点开始处理。注意,只有输入源支持源端缓冲(如Kafka和Flume),数据才能在之前的应用程序已经关闭但是升级后的应用成还没启动时进行缓冲。

监控应用程序

除了Spark的监控功能,Spark Streaming还有特定的监控功能。当使用StreamingContext时,Spark web UI有一个额外的Streaming tab页,显示关于正在运行的receiver的统计数据(receiver是否活跃,接收到记录的数量,receiver错误等)以及完成的批次信息(批次处理时间,队列延迟等)。这些都可以监控streaming应用程序的进程。

web UI中下面两个指标尤为重要:

  • Processing Time - 处理每个批次数据的时间。
  • Scheduling Delay - 批次在队列中等待前一个批次处理完成的时间

如果批处理时间总是大于批时间间隔和/或排队延迟持续增加,则说明系统不能快速处理这些批次,正在落后。在这种情况下,要考虑减少批处理时间。

Spark Streaming的进程也可以使用StreamingListener接口进行监控,这个接口允许获取receiver的状态以及处理时间。注意这是一个开发者API,未来会改进(报告更多的信息)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容