DStream, DStreamGraph 详解

引言
我们在前面的文章讲过,Spark Streaming 的 模块 1 DAG 静态定义 要解决的问题就是如何把计算逻辑描述为一个 RDD DAG 的“模板”,在后面 Job 动态生成的时候,针对每个 batch,都将根据这个“模板”生成一个 RDD DAG 的实例。

在 Spark Streaming 里,这个 RDD “模板”对应的具体的类是 DStream,RDD DAG “模板”对应的具体类是 DStreamGraph。

DStream的全限定名是:org.apache.spark.streaming.dstream.DStream
DStreamGraph的全限定名是:org.apache.spark.streaming.DStreamGraph

本文涉及的类在 Spark Streaming 中的位置如上图所示;下面详解 DStream, DStreamGraph。

DStream, transformation, output operation 解析
回想一下,RDD 的定义是一个只读、分区的数据集(an RDD is a read-only, partitioned collection of records),而 DStream 又是 RDD 的模板,所以我们把 Dstream 也视同数据集。

我们先看看定义在这个 DStream 数据集上的转换(transformation)和 输出(output)。
现在假设我们有一个 DStream 数据集 a:

val a = new DStream()
那么通过 filter() 操作就可以从 a 生成一个新的 DStream 数据集 b:

val b = a.filter(func)
这里能够由已有的 DStream 产生新 DStream 的操作统称 transformation。一些典型的 tansformation 包括 map(), filter(), reduce(), join() 等 。

另一些不产生新 DStream 数据集,而是只在已有 DStream 数据集上进行的操作和输出,统称为 output。比如 a.print() 就不会产生新的数据集,而是只是将 a 的内容打印出来,所以 print() 就是一种 output 操作。一些典型的 output 包括 print(), saveAsTextFiles(), saveAsHadoopFiles(), foreachRDD() 等。

一段 quick example 的 transformation, output 解析
我们看一下 Spark Streaming 官方的 quick example 的这段对 DStream DAG 的定义,注意看代码中的注释讲解内容:在这里插入图片描述
也就是 lines.flatMap(_.split(" ")) 将 new 出来一个 DStream 具体子类 FlatMappedDStream 的实例。
在这里插入图片描述
Dependency, DStreamGraph 解析
先再次回过头来看一下 transformation 操作。当我们写代码 c = a.join(b), d = c.filter() 时, 它们的 DAG 逻辑关系是 a/b → c,c → d,但在 Spark Streaming 在进行物理记录时却是反向的 a/b ← c, c ← d,如下图:
在这里插入图片描述
那物理上为什么不顺着 DAG 来正向记录,却用反向记录?

这里背后的原因是,在 Spark Core 的 RDD API 里,RDD 的计算是被触发了以后才进行 lazy 求值的,即当真正求 d 的值的时候,先计算上游 dependency c;而计算 c 则先进一步计算 c 的上游 dependency a 和 b。Spark Streaming 里则与 RDD DAG 的反向表示保持了一致,对 DStream 也采用的反向表示。

所以,这里 d 对 c 的引用,表达的是一个上游依赖(dependency)的关系;也就是说,不求值则已,一旦 d.print() 这个 output 操作触发了对 d 的求值,那么就需要从 d 开始往上游进行追溯计算。

具体的过程是,d.print() 将 new 一个 d 的一个下游 ForEachDStream x —— x 中记明了需要做的操作 func = print() —— 然后在每个 batch 动态生成 RDD 实例时,以 x 为根节点、进行一次 BFS(宽度优先遍历),就可以快速得到需要进行实际计算的最小集合。如下图所示,这个最小集合就是 {a, b, c, d}。
在这里插入图片描述
再看一个例子。如下图所示,如果对 d, f 分别调用 print() 的 output 操作,那么将在 d, f 的下游分别产生新的 DStream x, y,分别记录了具体操作 func = print()。在每个 batch 动态生成 RDD 实例时,就会分别对 x 和 y 进行 BFS 遍历,分别得到上游集合 {a,b,c,d} 和 {b,e,f}。作为对比,这里我们不对 h 进行 print() 的 output 操作,所以 g, h 将得不到遍历。
在这里插入图片描述

通过以上分析,我们总结一下:

(1) DStream 逻辑上通过 transformation 来形成 DAG,但在物理上却是通过与 transformation 反向的依赖(dependency)来构成表示的

(2) 当某个节点调用了 output 操作时,就产生一个新的 ForEachDStream ,这个新的 ForEachDStream 记录了具体的 output 操作是什么

(3) 在每个 batch 动态生成 RDD 实例时,就对 (2) 中新生成的 DStream 进行 BFS 遍历

我们将在 (2) 中,由 output 操作新生成的 DStream 称为 output stream。

最后,我们给出:

(4) Spark Streaming 记录整个 DStream DAG 的方式,就是通过一个 DStreamGraph 实例记录了到所有的 output stream 节点的引用

通过对所有 output stream 节点进行遍历,就可以得到所有上游依赖的 DStream不能被遍历到的 DStream 节点 —— 如 g 和 h —— 则虽然出现在了逻辑的 DAG 中,但是并不属于物理的 DStreamGraph,也将在 Spark Streaming 的实际运行过程中不产生任何作用
(5) DStreamGraph 实例同时也记录了到所有 input stream 节点的引用

DStreamGraph 时常需要遍历没有上游依赖的 DStream 节点 —— 称为 input stream —— 记录一下就可以避免每次为查找 input stream 而对 output steam 进行 BFS 的消耗
我们本节所描述的内容,用下图就能够总结了:
在这里插入图片描述

完全参考自腾讯广告团队酷玩Spark的知识总结,本人觉得文章写的非常好,能收获许多知识,仅是为了方便阅读,将其复制到CSDN平台。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值