Spark Streaming 一文读懂

Spark Streaming:流计算框架

以往,批处理和流计算被看作大数据系统的两个方面。我们常常能看到这样的架构——以 Kafka、Storm 为代表的流计算框架用于实时计算,而 Spark 或 MapReduce 则负责每天、每小时的数据批处理。在 ETL 等场合,这样的设计常常导致同样的计算逻辑被实现两次,耗费人力不说,保证一致性也是个问题。

Spark Streaming 正是诞生于此类需求。传统的流计算框架大多注重于低延迟,采用了持续的(continuous)算子模型;而 Spark Streaming 基于 Spark,另辟蹊径提出了 D-Stream(Discretized Streams)方案:将流数据切成很小的批(micro-batch),用一系列的短暂、无状态、确定性的批处理实现流处理。

这里的批处理引擎是Spark Core,也就是把Spark Streaming的输入数据按照batch size(如1秒)分成一段一段的数据(Discretized Stream)。

Spark Streaming 一文读懂_第1张图片

Spark Streaming 的做法在流计算框架中很有创新性,它虽然牺牲了低延迟(一般流计算能做到 100ms 级别,Spark Streaming 延迟一般为 1s 左右),但是带来了三个诱人的优势:

  • 更高的吞吐量(大约是 Storm 的 2-5 倍)
  • 更快速的失败恢复(通常只要 1-2s)。SparkStreaming在没有额外代码和配置的情况下可以恢复丢失的工作,因此对于 straggler(性能拖后腿的节点)直接杀掉即可
  • 可以融合到spark生态系统。开发者只需要维护一套 ETL 逻辑,即可同时用于批处理和流计算

Spark Streaming 一文读懂_第2张图片

 

▲ 上左图中,为了在持续算子模型的流计算系统中保证一致性,不得不在主备机之间使用同步机制,导致性能损失;右图是 D-Stream 的原理示意图,Spark Streaming 完全没有这个问题。

你可能会困惑,流计算中的状态一直是个难题。但我们刚刚提到 D-Stream 方案是无状态的,那诸如 word count 之类的问题,怎么做到保持 count 算子的状态呢?

答案是通过 RDD:将前一个时间步的 RDD 作为当前时间步的 RDD 的前继节点,就能造成状态不断更替的效果。实际上,新的状态 RDD 总是不断生成,而旧的 RDD 并不会被“替代”,而是作为新 RDD 的前继依赖。对于底层的 Spark 框架来说,并没有时间步的概念,有的只是不断扩张的 DAG 图和新的 RDD 节点。

Spark Streaming 一文读懂_第3张图片

▲ 上图是流式计算 word count 的例子,count 结果在不同时间步中不断累积。

那么另一个问题也随之而来:随着时间的推进,上图中的状态 RDD counts会越来越多,他的祖先(lineage)变得越来越长,极端情况下,恢复过程可能溯源到很久之前。这是不可接受的!因此,Spark Streming 会定期地对状态 RDD 做 checkpoint,将其持久化到 HDFS 等存储中,这被称为 lineage cut,在它之前更早的 RDD 就可以没有顾虑地清理掉了。

关于流行的几个开源流计算框架的对比,可以参考文章 Comparison of Apache Stream Processing Frameworks。

DStream

  Discretized Stream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据,如下图:

Spark Streaming 一文读懂_第4张图片

对数据的操作也是以RDD为单位来进行的:

Spark Streaming 一文读懂_第5张图片

 

  Spark Streaming使用数据源产生的数据流创建DStream,也可以在已有的DStream上使用一些操作来创建新的DStream。

  它的工作流程像下面的图所示一样,接受到实时数据后,给数据分批次,然后传给Spark Engine处理最后生成该批次的结果。

Spark Streaming 一文读懂_第6张图片

 

DStream相关操作

DStream上的操作与RDD的类似,分为Transformations(转换)和Output Operations(输出)两种。此外转换操作中还有一些比较特殊的操作,如:updateStateByKey()、transform()以及各种Window相关的操作。

Transformations on DStreams

Transformation

Meaning

map(func)

对DStream中的各个元素进行func函数操作,然后返回一个新的DStream

flatMap(func)

与map方法类似,只不过各个输入项可以被输出为零个或多个输出项

filter(func)

过滤出所有函数func返回值为true的DStream元素并返回一个新的DStream

repartition(numPartitions)

增加或减少DStream中的分区数,从而改变DStream的并行度

union(otherStream)

将源DStream和输入参数为otherDStream的元素合并,并返回一个新的DStream.

count()

通过对DStream中的各个RDD中的元素进行计数,然后返回只有一个元素的RDD构成的DStream

reduce(func)

对源DStream中的各个RDD中的元素利用func进行聚合操作,然后返回只有一个元素的RDD构成的新的DStream.

countByValue()

对于元素类型为K的DStream,返回一个元素为(K,Long)键值对形式的新的DStream,Long对应的值为源DStream中各个RDD的key出现的次数

reduceByKey(func, [numTasks]) 

利用func函数对源DStream中的key进行聚合操作,然后返回新的(K,V)对构成的DStream

join(otherStream, [numTasks])

输入为(K,V)、(K,W)类型的DStream,返回一个新的(K,(V,W))类型的DStream

cogroup(otherStream, [numTasks])

输入为(K,V)、(K,W)类型的DStream,返回一个新的 (K, Seq[V], Seq[W]) 元组类型的DStream

transform(func)    

通过RDD-to-RDD函数作用于DStream中的各个RDD,可以是任意的RDD操作,从而返回一个新的RDD

updateStateByKey(func)

根据key的之前状态值和key的新值,对key进行更新,返回一个新状态的DStream

特殊的Transformations:

updateStateByKey 用于记录历史记录,保存上次的状态

reduceByKeyAndWindow  开窗函数

 

Output Operations on DStreams

相当于rdd的action的作用,transformation不会立即执行,到这才会执行

Output Operation

Meaning

print()

打印到控制台

saveAsTextFiles(prefix, [suffix])

保存流的内容为文本文件,文件名为

"prefix-TIME_IN_MS[.suffix]".

saveAsObjectFiles(prefix, [suffix])

保存流的内容为SequenceFile,文件名为

 "prefix-TIME_IN_MS[.suffix]".

saveAsHadoopFiles(prefix, [suffix])

保存流的内容为hadoop文件,文件名为

 "prefix-TIME_IN_MS[.suffix]".

 

Spark 与 Storm 对比

Storm是来一条数据处理一条数据

Spark Streaming是以某一时间间隔批量处理数据

 

Spark Structured Streaming:流计算与 SQL

Spark 通过 Spark Streaming 拥有了流计算能力,那 Spark SQL 是否也能具有类似的流处理能力呢?答案是肯定的,只要将数据流建模成一张不断增长、没有边界的表,在这样的语义之下,很多 SQL 操作等就能直接应用在流数据上。

出人意料的是,Spark Structured Streaming 的流式计算引擎并没有复用 Spark Streaming,而是在 Spark SQL 上设计了新的一套引擎。因此,从 Spark SQL 迁移到 Spark Structured Streaming 十分容易,但从 Spark Streaming 迁移过来就要困难得多。

很自然的,基于这样的模型,Spark SQL 中的大部分接口、实现都得以在 Spark Structured Streaming 中直接复用。将用户的 SQL 执行计划转化成流计算执行计划的过程被称为增量化(incrementalize),这一步是由 Spark 框架自动完成的。对于用户来说只要知道:每次计算的输入是某一小段时间的流数据,而输出是对应数据产生的计算结果。

Spark Streaming 一文读懂_第7张图片

▲ 左图是 Spark Structured Streaming 模型示意图;右图展示了同一个任务的批处理、流计算版本,可以看到,除了输入输出不同,内部计算过程完全相同。

 

与 Spark SQL 相比,流式 SQL 计算还有两个额外的特性,分别是窗口(window)和水位(watermark)。

窗口(window)是对过去某段时间的定义。批处理中,查询通常是全量的(例如:总用户量是多少);而流计算中,我们通常关心近期一段时间的数据(例如:最近24小时新增的用户量是多少)。用户通过选用合适的窗口来获得自己所需的计算结果,常见的窗口有滑动窗口(Sliding Window)、滚动窗口(Tumbling Window)等。

水位(watermark)用来丢弃过早的数据。在流计算中,上游的输入事件可能存在不确定的延迟,而流计算系统的内存是有限的、只能保存有限的状态,一定时间之后必须丢弃历史数据。以双流 A JOIN B 为例,假设窗口为 1 小时,那么 A 中比当前时间减 1 小时更早的数据(行)会被丢弃;如果 B 中出现 1 小时前的事件,因为无法处理只能忽略。

Spark Streaming 一文读懂_第8张图片

 

▲ 上图为水位的示意图,“迟到”太久的数据(行)由于已经低于当前水位无法处理,将被忽略。

水位和窗口的概念都是因时间而来。在其他流计算系统中,也存在相同或类似的概念。

关于 SQL 的流计算模型,常常被拿来对比的还有另一个流计算框架 Apache Flink。与 Spark 相比,它们的实现思路有很大不同,但在模型上是很相似的。

总结

Spark Streaming 的 D-Stream 本质上也是将输入数据分成一个个 micro-batch 的 RDD。Spark Structured Streaming 是 Spark SQL 的流计算版本,它将输入的数据流看作不断追加的数据行。

你可能感兴趣的:(Spark)