RDD(未完成)

RDD

Resilient Distributed Dataset:弹性分布式数据集
可存储计算逻辑的一种数据集

RDD特性

  1. 可分区:提高消费能力,更适合并行计算
  2. 弹性:
    • 存储的弹性:内存和磁盘的自动切换;
    • 容错的弹性:数据丢失可自动恢复;
    • 计算的弹性:计算出错重试机制;
    • 分片的弹性:可根据需要重新分片。
  3. 分布式:数据存储在大数据集群不同节点上
  4. 不可变性:计算逻辑不可变,一旦改变将产生新的RDD
  5. 并行性:
  6. 数据集:封装了计算逻辑,并不保存数据

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)
  1. 分区列表
  2. 分区计算函数:相同的计算函数在不同的分区
  3. RDD之间的依赖关系
  4. (可选的)分区器
  5. (可选的)首选位置:计算数据的位置

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,Iterable))类型的RDD

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。

RDD分区器
posted @ 2020-06-11 14:06  yltf  阅读(200)  评论(0)    收藏  举报