RDD 的成员之一是依赖集,依赖集也关系到任务调度
源码
Dependency代码主要在一个源文件中:core/Dependency代码中有5个类。
除此以外在 core/rdd/PartitionPruningRDD还有一个PruneDependency类。
他们的名称和继承关系如下图:
通过阅读代码可以得到以下信息:
- 依赖的根类是
Dependency
,只有一个RDD 成员,表示依赖的对象。这类继承了Serializable
类,是可以序列化的。 - 依赖分为两大种,一个叫窄依赖(
Narrow
),另一个就洗牌依赖(Shuffle
,很多材料也叫作“宽”依赖)。 - 从数量关系上说,“1-to-1”(
OneToOne
)、“n-to-1”(Range
)、“1-to-部分分区”(Prune
,剪枝)是窄依赖,宽依赖是“n-to-n”的。 - “1-to-1”是RDD的默认依赖。上节中的
MapPartitionRDD
是一对一的转换,就包含“1-to-1“依赖。 - ”n-to-1“的依赖只有一个使用场景——
UnionRDD
,“交”运算,多个 RDD 合并到一个 RDD 中。 - 剪枝依赖是个私有对象,用于优化,减少数据载入。
- 洗牌依赖复杂一些。
只有 RDD 的转换(Transformations)才用到依赖,RDD 的操作(Actions,如reduce、collect等)就不需要依赖,直接运行SparkContext.runjob()
函数。RDD 有哪些转换和操作?阅读文档: Spark Programming Guide - Spark 2.0.2 Documentation
洗牌依赖
洗牌依赖也只是相对复杂,代码也不长。
@DeveloperApi
class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
@transient private val _rdd: RDD[_ <: Product2[K, V]],
val partitioner: Partitioner,
val serializer: Serializer = SparkEnv.get.serializer,
val keyOrdering: Option[Ordering[K]] = None,
val aggregator: Option[Aggregator[K, V, C]] = None,
val mapSideCombine: Boolean = false)
extends Dependency[Product2[K, V]] {
override def rdd: RDD[Product2[K, V]] = _rdd.asInstanceOf[RDD[Product2[K, V]]]
private[spark] val keyClassName: String = reflect.classTag[K].runtimeClass.getName
private[spark] val valueClassName: String = reflect.classTag[V].runtimeClass.getName
// Note: It's possible that the combiner class tag is null, if the combineByKey
// methods in PairRDDFunctions are used instead of combineByKeyWithClassTag.
private[spark] val combinerClassName: Option[String] = Option(reflect.classTag[C]).map(_.runtimeClass.getName)
val shuffleId: Int = _rdd.context.newShuffleId()
val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle(shuffleId, _rdd.partitions.length, this)
_rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this))
}
是的,只有8行代码,核心内容是:
- 这个类有三个泛型类型,
K
=key,V
=value,C
=combiner; - 洗牌依赖只能用于Product2[K, V]及其父类,即 KV 数据;
- 成员有 分区器(partitioner) 、序列器(serializer)、排序器(keyOrdering)、聚合器(aggregator)、map 端聚合开关(mapSideCombine);
-
_rdd.context.newShuffleId()
获得一个自增的 ID; -
_rdd.context.env.shuffleManager.registerShuffle
获得几个洗牌的句柄。通过core/shuffle/sort/SortShuffleManager代码可以知道,一共有三种句柄:- 分区数很少(小于变量
spark.shuffle.sort.bypassMergeThreshold
,默认200)时,用BypassMergeSortShuffleHandle
,直接发送数据合并,不用耗时的序列化和反序列化; - 否则,如果能序列化,则用
SerializedShuffleHandle
,用序列化和反序列化,降低网络 IO; - 否则,使用基础的
BaseShuffleHandle
。
- 分区数很少(小于变量
Scala语法
在 Java 中使用null 是非常容易出错的,在 Guava( GitHub - google/guava: Google Core Libraries for Java 6+ ) 中提供了 Optional 来避免使用 null。
同样的 Scala 自带了 Option 来避免使用 null。
Option 有两个子类,Some 和 None
scala> val capitals = Map("France"->"Paris", "Japan"->"Tokyo", "China"->"Beijing")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing)
scala> capitals get "France"
res0: Option[String] = Some(Paris)
scala> capitals get "North Pole"
res1: Option[String] = None
None.get 会报错,用 getOrElse 可以给取不到值的时候赋默认值:
scala> capitals get "North Pole" get
warning: there was one feature warning; re-run with -feature for details
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:347)
at scala.None$.get(Option.scala:345)
... 33 elided
scala> capitals get "France" get
warning: there was one feature warning; re-run with -feature for details
res3: String = Paris
scala> (capitals get "North Pole") getOrElse "Oops"
res7: String = Oops
scala> capitals get "France" getOrElse "Oops"
res8: String = Paris
在 Spark 中大量使用了 Option。
疑问列表
我将阅读过程中的未解内容记录下来,留待以后阅读代码时解答。疑问一个一个划掉,就是成长的过程。
- reduce 等 RDD 操作是如何执行的?
- groupByKey 等洗牌操作是如何执行的?不同的洗牌类型有什么用?
结论
- 在 RDD 的转换时,会用到依赖
- 依赖包括窄依赖(1-to-1、n-to-1关系)、洗牌依赖(n-to-n 关系)
- 洗牌依赖包含分区器、序列器、排序器、聚合器、map聚合开关、ID、洗牌类型句柄等成分组成。洗牌类型句柄有三种。
本文源码
Dependency spark/Dependency.scala at master · apache/spark · GitHub
三种洗牌句柄 spark/SortShuffleManager.scala at master · apache/spark · GitHub
Option示例来自: 【Scala】使用Option、Some、None,避免使用null -