2021SC@SDUSC

前言

上篇博客大致介绍了Spark Streaming的设计思想,接下来对Spark Streaming整体架构进行分析

Spark Streaming整体架构

Spark Streaming将流式计算分解成一系列短小的批处理作业。这里的批处理引擎是Spark Core,也就是把Spark Streaming的输入数据按照Batch Interval分成一段一段的数据(DStream),每一段数据都转换成Spark中的RDD,然后将Spark Streaming中对DStream的转换操作变为针对Spark中对RDD的转换操作,将RDD经过操作变成中间结果保存在内存中。整个流式计算根据业务的需求可以对中间的结果进行叠加或者将结果存储到外部设备。下图显示了Spark Streaming的整体架构。

spark 窗口函数使用_spark

Spark Streaming的整体架构

在Spark Streaming架构中,还要解决好容错性问题。

对于流式计算来说,容错性至关重要。首先要介绍一下Spark中的RDD的容错机制。每一个RDD都是一个不可变的分布式可重算的数据集,其记录着确定性的操作机车搞关系,所以只要输入数据是可容错的,那么任意一个RDD的分区出错或不可用,都是可以利用原始输入数据通过转换操作而重新计算出的。

对于Spark Streaming来说,其RDD的传承关系如下图所示,图中的每一个环形表示一个RDD,环形中的每个圆形代表一个RDD中的一个Partition,图中每一列的多个RDD表示一个Dstream,而每一行最后一个RDD则表示每一个Batch Size所产生的中间结果RDD。可以看到图中的每一个RDD或是来自网络的数据流都能保证容错性,所以RDD中任意的Partition出错,都可以并行地在其他机器上将缺失的Partition计算出来。

spark 窗口函数使用_数据_02

Spark Streaming应用案例中RDD的传承关系

除了以上Spark本身的RDD容错机制,Spark Streaming还有与自身特点相关的容错问题。Job运行在Spark Cluster之上,系统容错更复杂又至关重要。计算性能不好 时,必须能限流和动态的调整资源。特别是在复杂的计算后,要设置检查点。有时我们希望流进来的数据一定会被处理,而且只处理一次。在处理出现崩溃的情况下,要保证exactly-once的事务语义。这些容错机制在此暂时不进行详细描述。

编程接口

Spark Streaming的编程接口主要由StreamingContext和DStream提供。如果要设计复杂的Spark应用程序,彻底理解StreamingContext和DStream很重要。
StreamingContext类是Spark Streaming的入口类,一定会使用Spark应用程序的入口类SparkContext。

//StreamingContext代码片段

class StreamingContext private[streaming] (
	sc_: SparkContext,
	cp_: Checkpoint,
	batchDur_: Duration
) extends Logging {
	...
	}

StreamingContext有SparkContext对象、检查点、时间间隔等参数。
流数据的处理是通过InputDStream子类的生成、DStream子类的操作来实现。
再看看DStream:

//DStream代码片段

abstract class DStream[T: ClassTag] (
	@transient private[streaming] var ssc: StreamingContext
 ) extends Serializable with Logging {


validateAtInit()

//=============================================================================
//Methods that should be implemented by subclasses of DStream\
//=============================================================================


/** Time interval after which the DStream generates a RDD */
def slideDuration: Duration

/** List of parent DStreams on which this DStream depends on */
def dependencies: List[DStream[_]]

/** Method that generated a RDD for the given time */
def compute(validTime: Time): Option[RDD[T]]

从这里可以看出,Dstream就是Spark Streaming的核心,就像RDD是Spark Core的核心,它有dependencies和compute。它们体现了DStream的三个关键特点:
1.除了第一个DStream,后面的DStream都要依赖前面的DStream。
2.DStream在每一个时间间隔都会生成一个RDD。
3.这类里有一个函数可以在每一个时间间隔后产生一个RDD

DStream子类会覆写compute,其参数为Time类型。Time类的成员是一个代表时刻的单位为毫秒的长整型数。在Spark Streaming中,Time对象通常用来对应某个批次。

继续观察DStream代码:

//DStream代码片段

//RDDs generated, marked as private[streaming] so that testsuites can access it
@transient
private[streaming] var generatedRDDs = new HashMap[Time, RDD[T]] ()

这里的generatedRDDs是一个HashMap,以时刻为key,以RDD为value。其中的每个RDD实际是该DStream在每一个批次所产生的相应RDD。Dstream就是RDD的模板。
DStream有很多子类,在此不一一举例。其 通过转换操作能够生成新的DStream子类对象。如DStream的输出操作会生成ForeachDStream的对象。
此后在StreamingContext启动时,会利用这些DStream子类对象来触发RDD的生成和转换,然后产生若干个Job,在集群上运行。
DStream是逻辑级别的操作流对象,而RDD是物理级别的,DStream所表达的最终都转换成RDD去实现。前者是更高级的抽象,后者是底层的实现。DStream实际上就是在时间维度上对RDD集合的封装,DStream与RDD的关系就是随着时间流逝不断产生RDD,对DStream的操作就是在固定时间上操作RDD。