RDD(未完成)
RDD
Resilient Distributed Dataset:弹性分布式数据集
可存储计算逻辑的一种数据集
RDD特性
- 可分区:提高消费能力,更适合并行计算
- 弹性:
- 存储的弹性:内存和磁盘的自动切换;
- 容错的弹性:数据丢失可自动恢复;
- 计算的弹性:计算出错重试机制;
- 分片的弹性:可根据需要重新分片。
- 分布式:数据存储在大数据集群不同节点上
- 不可变性:计算逻辑不可变,一旦改变将产生新的RDD
- 并行性:
- 数据集:封装了计算逻辑,并不保存数据
RDD执行原理
装饰者设计模式
RDD在整个流程中主要用于将逻辑进行封装,并生成Task发送给Executor节点执行计算
RDD核心属性
* - 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之间的依赖关系
- (可选的)分区器
- (可选的)首选位置:计算数据的位置
makeRDD分区
内存中数据的分区基本上是平均分,如果不能整除,会采用一个基本的算法实现分配
def positions(length: Long, numSlices: Int): Iterator[(Int,
Int)] = { (0 until numSlices).iterator.map { i => val start = ((i * length) / numSlices).toInt val end = (((i + 1) * length) / numSlices).toInt (start, end) }}
RDD转换算子
一,value类型
1. map
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
def map[U: ClassTag](f: T => U): RDD[U]
2. mapPartitions
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
3. mapPartitionsWithIndex
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
4. groupBy
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
返回值是元组
- 元组中的第一个元素,表示分组的key
- 元组中的第二个元素,表示相同key的数据可形成的可迭代的集合
如果将上游的分区数据打乱重新组合到下游的分区中,那么这个操作称之为shuffle
如果数据被打乱重新组合,那么数据就可能出现不均匀的情况,可以改变下游RDD分区的数量。(追加分区数量参数)
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
5. flatMap
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
6. glom
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
def glom(): RDD[Array[T]]
7. filter
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
def filter(f: T => Boolean): RDD[T]
8. sample
根据指定的规则从数据集中抽取数据
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
9. distinct
将数据集中重复的数据去重
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
10. coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当spark程序中,存在过多的小任务的时候,可以通过coalesce方法,收缩合并分区,减少分区的个数,减小任务调度成本
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
11. repartition
该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。无论是将分区数多的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,repartition操作都可以完成,因为无论如何都会经shuffle过程。
让数据更均衡一些,可以有效防止数据倾斜问题。
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
12. sortBy
该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。排序后新产生的RDD的分区数与原RDD的分区数一致。
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
13. pipe
管道,针对每个分区,都调用一次shell脚本,返回输出的RDD。
注意:shell脚本需要放在计算节点可以访问到的位置
def pipe(command: String): RDD[String]
二,双value类型
1. intersection
求交集
def intersection(other: RDD[T]): RDD[T]
2. union
求并集
def union(other: RDD[T]): RDD[T]
3. subtract
求差集
def subtract(other: RDD[T]): RDD[T]
4. zip
将两个RDD中的元素,以键值对的形式进行合并。其中,键值对中的Key为第1个RDD中的元素,Value为第2个RDD中的元素。
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
三,key-value类型
1. partitionBy
将数据按照指定Partitioner重新进行分区。Spark默认的分区器是HashPartitioner
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
2. reduceByKey
可以将数据按照相同的Key对Value进行聚合
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
3. groupByKey
按key分组,返回value迭代器
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
4. aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
5. foldByKey
当分区内规则和分区间规则相同时,可以用foldByKey替换aggregateByKey
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
6. combineByKey
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
7. sortByKey
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)]
8. join
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素连接在一起的(K,(V,W))的RDD
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
9. leftOutJoin
类似于SQL语句的左外连接
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
10. cogroup
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
RDD行动算子
1. reduce
聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据
def reduce(f: (T, T) => T): T
2. collect
在驱动程序中,以数组Array的形式返回数据集的所有元素
def collect(): Array[T]
3. count
返回RDD中元素的个数
def count(): Long
4. first
返回RDD中的第一个元素
def first(): T
5. take
返回一个由RDD的前n个元素组成的数组
def take(num: Int): Array[T]
6. takeOrdered
返回该RDD排序后的前n个元素组成的数组
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
7. aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
8. fold
折叠操作,aggregate的简化版操作
def fold(zeroValue: T)(op: (T, T) => T): T
9. countByKey
统计每种key的个数
def countByKey(): Map[K, Long]
10. save相关
def saveAsTextFile(path: String): Unit
def saveAsObjectFile(path: String): Unit
def saveAsSequenceFile(
path: String,
codec: Option[Class[_ <: CompressionCodec]] = None): Unit
11. foreach
分布式遍历RDD中的每一个元素,调用指定函数
def foreach(f: T => Unit): Unit = withScope {
val cleanF = sc.clean(f)
sc.runJob(this, (iter: Iterator[T]) => iter.foreach(cleanF))
}
行动算子
RDD(分布式)的方法叫算子
foreach算子可以将循环在不同节点的数据计算完成
算子的逻辑代码是在分布式计算节点Executor执行的
算子以外的代码是在Driver端执行
方法
集合的方法的代码在当前节点中执行
foreach方法是在当前节点的内存中完成数据的循环
collect方法会将所有分区计算的结果拉取到当前节点的内存中,可能会内存溢出
RDD序列化
1. 闭包原则
从计算的角度, 算子以外的代码都是在Driver端执行, 算子里面的代码都是在Executor端执行。那么在scala的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给Executor端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测。Scala2.12版本后闭包编译方式发生了改变
2. kryo序列化
Kryo速度是Serializable的10倍。当RDD在Shuffle数据的时候,简单数据类型、数组和字符串类型已经在Spark内部使用Kryo来序列化。
注意:即使使用Kryo序列化,也要继承Serializable接口。
3. 依赖
窄依赖:窄依赖表示每一个父RDD的Partition最多被子RDD的一个Partition使用
宽依赖:宽依赖表示同一个父RDD的Partition被多个子RDD的Partition依赖,会引起Shuffle
除了窄依赖都是宽依赖
4. 阶段的划分
以落盘为分界,划分阶段
5. 任务划分
RDD任务切分中间分为:Application、Job、Stage和Task
Application:初始化一个SparkContext即生成一个Application;
Job:一个Action算子就会生成一个Job;
Stage:Stage等于宽依赖(ShuffleDependency)的个数加1;
Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。
注意:Application->Job->Stage->Task每一层都是1对n的关系。
6. 持久化
1. 缓存
RDD通过Cache或者Persist方法将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在JVM的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的action算子时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
Spark会自动对一些Shuffle操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点Shuffle失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist或cache。
2. checkpoint
所谓的检查点其实就是通过将RDD中间结果写入磁盘
由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。
checkpoint和cache的区别
1)Cache缓存只是将数据保存起来,不切断血缘依赖。Checkpoint检查点切断血缘依赖。
2)Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。
3)建议对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,否则需要再从头计算一次RDD。

浙公网安备 33010602011771号