RDD
1.注释
org.apache.spark.rdd.RDD
类源代码中有详细的注释:
-
A Resilient Distributed Dataset (RDD), the basic abstraction in Spark.
翻译:弹性的 分布式 数据集是 Spark 基础的抽象。
解释:弹性的(可复原的),说明数据集具有容错性、可修复性。分布式,说明数据集可以分布在不同的机器上
-
Represents an immutable, partitioned collection of elements that can be operated on in parallel.
翻译:RDD 是不可变的 分区的 可并行处理的 元素集合
解释:不可变的,这和 Scala 的设计理念相同,数据集一旦构建完成,就不能再修改,这样能轻松解决多个线程读数据的一致性问题。分区的=可并行处理的=分布式
This class contains the basic operations available on all RDDs, such as
map
,filter
, andpersist
.
翻译:这个抽象类包含了所有 RDD 都应该有的基本操作,比如map
、filter
、persist
等
解释:这三个操作分别是:批量转换、筛选、持久化In addition, [[org.apache.spark.rdd.PairRDDFunctions]] contains operations available only on RDDs of key-value
pairs, such asgroupByKey
andjoin
;
翻译:另外PairRDDFunctions
对象中包含了 键值对型(KV型) RDD 的操作,例如groupByKey
和join
;
解释:KV 型可以支持按 Key 分组、关联等操作[[org.apache.spark.rdd.DoubleRDDFunctions]] contains operations available only on RDDs of
Doubles;
翻译:DoubleRDDFunctions
提供可 double 数据集的操作;
解释:数值型数据集有求和、平均、分布图等统计性操作and [[org.apache.spark.rdd.SequenceFileRDDFunctions]] contains operations available on RDDs that
can be saved as SequenceFiles.
翻译:SequenceFileRDDFunctions
提供了顺序存储操作All operations are automatically available on any RDD of the right type (e.g. RDD[(Int, Int)]) through implicit.
翻译:所有的的类通过隐式转换自动地用于RDD实例中
解释:RDD 伴生对象里包含了隐式转换函数,用implicit
修饰。隐式转换是 Scala 的语法特性。-
Internally, each RDD is characterized by five main properties:
A list of partitions
A function for computing each split
A list of dependencies on other RDDs
Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
-
Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
翻译:在 RDD 中,包含这样的5个属性(也就说要实现抽象方法或给空对象赋值):
- 一个分区的列表(getPartitions)
- 一个用于计算分区中数据的函数(compute)
- 一个对其他 RDD 的依赖列表(getDependencies)
- 可选:KV 型 RDD 应该有一个分区器,例如 hash-分区器(partitioner)
- 可选:分区数据计算完后优先存储的位置,例如 HDFS 的某个块(getPreferredLocations)
初始参数:上下文和一组依赖
abstract class RDD[T: ClassTag](
@transient private var _sc: SparkContext,
@transient private var deps: Seq[Dependency[_]]
) extends Serializable with Logging
2.Dependency
Dependency代表了RDD之间的依赖关系,即血缘
继承关系如下:
RDD x 中每个 partition 可以依赖于 parent RDD 中一个或者多个 partition。而且这个依赖可以是完全依赖或者部分依赖。部分依赖指的是 parent RDD 中某 partition 中一部分数据与 RDD x 中的一个 partition 相关,另一部分数据与 RDD x 中的另一个 partition 相关。下图展示了完全依赖和部分依赖。
前三个是完全依赖,RDD x 中的 partition 与 parent RDD 中的 partition/partitions 完全相关。最后一个是部分依赖,RDD x 中的 partition 只与 parent RDD 中的 partition 一部分数据相关,另一部分数据与 RDD x 中的其他 partition 相关。
在 Spark 中,完全依赖被称为 NarrowDependency,部分依赖被称为 ShuffleDependency。其实 ShuffleDependency 跟 MapReduce 中 shuffle 的数据依赖相同(mapper 将其 output 进行 partition,然后每个 reducer 会将所有 mapper 输出中属于自己的 partition 通过 HTTP fetch 得到)。
- 第一种 1:1 的情况被称为 OneToOneDependency。
- 第二种 N:1 的情况被称为 N:1 NarrowDependency。
- 第三种 N:N 的情况被称为 N:N NarrowDependency。不属于前两种情况的完全依赖都属于这个类别。
- 第四种被称为 ShuffleDependency。
对于 NarrowDependency,具体 RDD x 中的 partitoin i 依赖 parrent RDD 中一个 partition 还是多个 partitions,是由 RDD x 中的 getParents(partition i)
决定(下图中某些例子会详细介绍)。还有一种 RangeDependency 的完全依赖,不过该依赖目前只在 UnionRDD 中使用,下面会介绍。
所以,总结下来 partition 之间的依赖关系如下:
- NarrowDependency (使用黑色实线或黑色虚线箭头表示)OneToOneDependency (1:1)NarrowDependency (N:1)NarrowDependency (N:N)RangeDependency (只在 UnionRDD 中使用)
- ShuffleDependency (使用红色箭头表示)
代码实现如下:
Dependency有两个子类,一个子类为窄依赖:NarrowDependency;一个为宽依赖ShuffleDependency
NarrowDependency也是一个抽象类,它实现了getParents 重写了 rdd 函数,它有两个子类,一个是 OneToOneDependency,一个是 RangeDependency
abstract class NarrowDependency[T](_rdd: RDD[T]) extends Dependency[T] {
/**
* Get the parent partitions for a child partition.
* @param partitionId a partition of the child RDD
* @return the partitions of the parent RDD that the child partition depends upon
*/
def getParents(partitionId: Int): Seq[Int]
override def rdd: RDD[T] = _rdd
}1234567891012345678910
OneToOneDependency,可以看到getParents实现很简单,就是传进一个partitionId: Int,再把partitionId放在List里面传出去,即去parent RDD 中取与该RDD 相同 partitionID的数据
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
override def getParents(partitionId: Int): List[Int] = List(partitionId)
}
RangeDependency,用于union。与上面不同的是,这里我们要算出该位置,设某个parent RDD 从 inStart 开始的partition,逐个生成了 child RDD 从outStart 开始的partition,则计算方式为: partitionId - outStart + inStart
class RangeDependency[T](rdd: RDD[T], inStart: Int, outStart: Int, length: Int)
extends NarrowDependency[T](rdd) {
override def getParents(partitionId: Int): List[Int] = {
if (partitionId >= outStart && partitionId < outStart + length) {
List(partitionId - outStart + inStart)
} else {
Nil
}
}
}12345678910111234567891011
ShuffleDependency,需要进行shuffle
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)
//获取shuffleID
val shuffleId: Int = _rdd.context.newShuffleId()
//向注册shuffleManager注册Shuffle信息
val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle(
shuffleId, _rdd.partitions.length, this)
_rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this))
}
)
分析如下:
- 这个类有三个泛型类型,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。
3.Partion
/**
* An identifier for a partition in an RDD.
*/
trait Partition extends Serializable {
/**
* Get the partition's index within its parent RDD
*/
def index: Int
// A better default implementation of HashCode
override def hashCode(): Int = index
override def equals(other: Any): Boolean = super.equals(other)
}
Partition具体表示RDD每个数据分区。
Partition提供trait类,内含一个index和hashCode()方法,具体子类实现与RDD子类有关
4.Partitioner
Partitioner决定KV形式的RDD如何根据key进行partition
abstract class Partitioner extends Serializable {
def numPartitions: Int
def getPartition(key: Any): Int
}
在ShuffleDependency里对应一个Partitioner,来完成宽依赖下,子RDD如何获取父RDD。
默认Partitioner
Partitioner的伴生对象提供defaultPartitioner方法,逻辑为:
传入的RDD(至少两个)中,遍历(顺序是partition数目从大到小)RDD,如果已经有Partitioner了,就使用。如果RDD们都没有Partitioner,则使用默认的HashPartitioner。而HashPartitioner的初始化partition数目,取决于是否设置了Spark.default.parallelism,如果没有的话就取RDD中partition数目最大的值。
如果上面这段文字看起来费解,代码如下:
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val rdds = (Seq(rdd) ++ others)
val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))
if (hasPartitioner.nonEmpty) {
hasPartitioner.maxBy(_.partitions.length).partitioner.get
} else {
if (rdd.context.conf.contains("spark.default.parallelism")) {
new HashPartitioner(rdd.context.defaultParallelism)
} else {
new HashPartitioner(rdds.map(_.partitions.length).max)
}
}
}
5.Persist
默认cache()过程是将RDD persist在内存里,persist()操作可以为RDD重新指定StorageLevel
class StorageLevel private(
private var useDisk_ : Boolean,
private var useMemory_ : Boolean,
private var useOffHeap_ : Boolean,
private var deserialized_ : Boolean,
private var replication_ : Int = 1)