深入理解Spark Streaming

Posted yszd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Spark Streaming相关的知识,希望对你有一定的参考价值。

一.DStream的两类操作

  DStream内部其实是RDD序列,所有的DStream操作最终都转换为RDD操作。通过分析源码,可以进一步窥探这种转换是如何进行的。

  DStream有一些与RDD类似的基础属性:

  • 依赖的其它DStream列表。
  • 生成RDD的时间间隔。
  • 一个名为compute的计算函数,用于生成RDD,类似于RDD的compute。

  DStream的操作分为两类,一类是Transformation操作,对应RDD的Transformation操作。以flatMap为例,DStream中的flatMap不过是返回一个新的DStream派生类FlatMappedDStream,这一点跟RDD的flatMap非常类似。DStream的flatMap定义如下:

  /**
   * Return a new DStream by applying a function to all elements of this DStream,
   * and then flattening the results
   */
  def flatMap[U: ClassTag](flatMapFunc: T => TraversableOnce[U]): DStream[U] = ssc.withScope {
    new FlatMappedDStream(this, context.sparkContext.clean(flatMapFunc))
  }

  而FlatMappedDStream的实现也很简单,主要作用是像RDD一样维护计算关系链,完整定义如下:

private[streaming]
class FlatMappedDStream[T: ClassTag, U: ClassTag](
    parent: DStream[T],
    flatMapFunc: T => TraversableOnce[U]
  ) extends DStream[U](parent.ssc) {

  override def dependencies: List[DStream[_]] = List(parent)

  override def slideDuration: Duration = parent.slideDuration

  override def compute(validTime: Time): Option[RDD[U]] = {
    parent.getOrCompute(validTime).map(_.flatMap(flatMapFunc))
  }
}

  其中compute调用DStream的getOrCompute方法用于读取RDD的内存,要么放到缓存中,要么调用接口函数compute计算生成。

  DStream另外一类操作是OutPut操作,Output操作才会触发DStream的实际执行,作用非常类似于RDD的Action操作,类如print操作,定义如下:

 /**
   * Print the first ten elements of each RDD generated in this DStream. This is an output
   * operator, so this DStream will be registered as an output stream and there materialized.
   */
  def print(): Unit = ssc.withScope {
    print(10)
  }

  /**
   * Print the first num elements of each RDD generated in this DStream. This is an output
   * operator, so this DStream will be registered as an output stream and there materialized.
   */
  def print(num: Int): Unit = ssc.withScope {
    def foreachFunc: (RDD[T], Time) => Unit = {
      (rdd: RDD[T], time: Time) => {
        val firstNum = rdd.take(num + 1)
        // scalastyle:off println
        println("-------------------------------------------")
        println(s"Time: $time")
        println("-------------------------------------------")
        firstNum.take(num).foreach(println)
        if (firstNum.length > num) println("...")
        println()
        // scalastyle:on println
      }
    }
    foreachRDD(context.sparkContext.clean(foreachFunc), displayInnerRDDOps = false)
  }

  DStream.print调用了RDD.take方法,而后者是一个Action操作,是不是所有的DStream输出操作最后都调用一个RDD的Action操作呢,看看saveAsTextFiles和saveAsObjectFiles,它们没有直接调用RDD Action操作【而是先调用一下rdd.saveAsTextFile】,然后通过foreachRDD来实现的,传入的函数中调用了RDD的Action。saveAsTextFiles的定义如下:

  /**
   * Save each RDD in this DStream as at text file, using string representation
   * of elements. The file name at each batch interval is generated based on
   * `prefix` and `suffix`: "prefix-TIME_IN_MS.suffix".
   */
  def saveAsTextFiles(prefix: String, suffix: String = ""): Unit = ssc.withScope {
    val saveFunc = (rdd: RDD[T], time: Time) => {
      val file = rddToFileName(prefix, suffix, time)
      rdd.saveAsTextFile(file)
    }
    this.foreachRDD(saveFunc, displayInnerRDDOps = false)
  }

  相比之下,另外一个最灵活的Output操作foreachRDD完全依赖传入的函数来实现功能,所以对于foreachRDD的使用至少要包含一个RDD Action调用。因为Spark Streaming的调度是由Output方法触发的,每个周期调用一次所有定义的Output方法。Output内部再调用RDD Action最终完成计算,否则程序只会接收数据,然后丢弃,不执行计算。

以上是关于深入理解Spark Streaming的主要内容,如果未能解决你的问题,请参考以下文章

Spark Streaming和Kafka集成深入浅出

Spark深入学习 -15Spark Streaming前奏-Kafka初体验

spark streaming 接收kafka消息之二 -- 运行在driver端的receiver

Spark Streaming之妙用foreachRDD和foreachPartition

Spark Streaming之妙用foreachRDD和foreachPartition

Spark Streaming中动态Batch Size深入及RateController解析