DStream中transform的理解误区与应用

文章目录

  • 简介
  • 理解上的误区
    • transform vs Transformation
    • transform中有RDD的action操作
    • transform在每个batch批次间隔间被调用
  • 实际应用

简介

DStream中的transform操作允许运行任何 RDD-to-RDD 函数。它能够被用来应用任何没在 DStream API 中提供的 RDD 操作。它和DStream其他普通的Transformation类操作的区别是可以使用所有RDD上的操作算子

而DStream中的output Operations类算子,如foreachRDD,会触发action操作生成具体的任务。

理解上的误区

transform vs Transformation

DStream上的操作也分为两大类:Transformation和Output Operations。其中transformation操作和RDD的transformation操作类似,是将DStream 输入的数据转换为另一个DStream,转换操作也是懒执行的,不会触发生成job任务。Output Operations操作是将DStream 的数据推到如数据库、文件系统等外部系统中去,从而触发生成实际的job任务。

transform是DStream的一种Transformation操作,其他DStream操作大都底层实际也是调用的transform。

transform中有RDD的action操作

transform是DStream的一种Transformation操作(实际在DStream子类的compute方法中执行),transform转换函数里是不能有关于rdd的action操作或者输出操作,有也不会执行,只能使用普通转换算子。输出操作只能在DStream的Output Operations类操作(实际在DStream子类的generateJob方法中执行)中。

transform在每个batch批次间隔间被调用

transform是在计算生成batch中的RDD数据时执行的,是在每个batch批次间隔间会调用此转化操作。从transform的代码可以看到,transform是将当前的DStream转换为另一个DStream(TransformedDStream),并将自身的函数传入。

DStream.scala 

/**
 * Return a new DStream in which each RDD is generated by applying a function
 * on each RDD of 'this' DStream.
 */
def transform[U: ClassTag](transformFunc: (RDD[T], Time) => RDD[U]): DStream[U] = ssc.withScope {
  // because the DStream is reachable from the outer object here, and because
  // DStreams can't be serialized with closures, we can't proactively check
  // it for serializability and so we pass the optional false to SparkContext.clean
  val cleanedF = context.sparkContext.clean(transformFunc, false)
  val realTransformFunc = (rdds: Seq[RDD[_]], time: Time) => {
    assert(rdds.length == 1)
    cleanedF(rdds.head.asInstanceOf[RDD[T]], time)
  }
  new TransformedDStream[U](Seq(this), realTransformFunc)
}

在TransformedDStream的compute方法中实际调用转换函数:

TransformedDStream.scala

override def compute(validTime: Time): Option[RDD[U]] = {
  val parentRDDs = parents.map { parent => parent.getOrCompute(validTime).getOrElse(
    // Guard out against parent DStream that return None instead of Some(rdd) to avoid NPE
    throw new SparkException(s"Couldn't generate RDD from parent at time $validTime"))
  }
  val transformedRDD = transformFunc(parentRDDs, validTime)
  if (transformedRDD == null) {
    throw new SparkException("Transform function must not return null. " +
      "Return SparkContext.emptyRDD() instead to represent no element " +
      "as the result of transformation.")
  }
  Some(transformedRDD)
}

实际应用

transform在每个batch批次间隔间被调用,可以执行随时间变化的RDD操作,如RDD转换操作,分区数,广播变量等可以在批次之间更改。官网给的一个例子:通过将输入数据流与预先计算的垃圾信息(也可以用Spark一起生成)进行实时数据清理,然后根据它进行过滤。

val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // 垃圾信息,要过滤的数据

val cleanedDStream = wordCounts.transform { rdd =>
  rdd.join(spamInfoRDD).filter(...) // 关联进行数据过滤
  ...
}

在数据计算过程中的一个常用操作是用事实数据关联维度数据,而维度数据的特点是小数据量且慢变的。可以在transform中实现关联维度数据,并不断更新维度数据:

def nextDeadline() : Long = {
  // assumes midnight on UTC timezone.
  LocalDate.now.atStartOfDay().plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli()
}
// Note this is a mutable variable!
var initRDD = sparkSession.read.parquet("/tmp/learningsparkstreaming/dimension.parquet")
// Note this is a mutable variable!
var _nextDeadline = nextDeadline()

val lines = ssc.socketTextStream("localhost", 9999)
val linesTransform = lines.transform(rdd => {
	if (System.currentTimeMillis > _nextDeadline) {
      initRDD = sparkSession.read.parquet("/tmp/learningsparkstreaming/dimension.parquet")
      _nextDeadline = nextDeadline()
    }
	rdd.join(initRDD)
})
// we use the foreachRDD as a scheduling trigger. 
linesTransform.foreachRDD{ _ => 
  ...
}

你可能感兴趣的:(spark)