本文介绍一下rdd的基本属性概念、rdd的转换/行动操作、rdd的宽/窄依赖。

RDD:Resilient Distributed Dataset 弹性分布式数据集,是Spark中的基本抽象。

RDD表示可以并行操作的元素的不变分区集合

RDD提供了许多基本的函数(map、filter、reduce等)供我们进行数据处理。

RDD概述

通常来说,每个RDD有5个主要的属性组成:

  • 分区列表

    RDD是由多个分区组成的,分区是逻辑上的概念。RDD的计算是以分区为单位进行的。

  • 用于计算每个分区的函数

    作用于每个分区数据的计算函数。

  • 对其他RDD的依赖关系列表

    RDD中保存了对于父RDD的依赖,根据依赖关系组成了Spark的DAG(有向无环图),实现了spark巧妙、容错的编程模型

  • 针对键值型RDD的分区器

    分区器针对键值型RDD而言的,将key传入分区器获取唯一的分区id。在shuffle中,分区器有很重要的体现。

  • 对每个分区进行计算的首选位置列表

    根据数据本地性的特性,获取计算的首选位置列表,尽可能的把计算分配到靠近数据的位置,减少数据的网络传输。

RDD的内部代码

先看看基本概念的代码:
//创建此RDD的SparkContext
def sparkContext: SparkContext = sc
// 唯一的id
val id: Int = sc.newRddId()
// rdd友善的名字
@transient var name: String = _
// 分区器
val partitioner: Option[Partitioner] = None
// 获取依赖列表
// dependencies和partitions中都用到了checkpointRDD,如果进行了checkpoint,checkpointRDD表示进行checkpoint后的rdd
final def dependencies: Seq[Dependency[_]] = {
// 一对一的窄依赖
checkpointRDD.map(r => List(new OneToOneDependency(r))).getOrElse {
if (dependencies_ == null) {
dependencies_ = getDependencies
}
dependencies_
}
}
// 获取分区列表
final def partitions: Array[Partition] = {
checkpointRDD.map(_.partitions).getOrElse {
if (partitions_ == null) {
partitions_ = getPartitions
partitions_.zipWithIndex.foreach { case (partition, index) =>
require(partition.index == index,
s"partitions($index).partition == ${partition.index}, but it should equal $index")
}
}
partitions_
}
}
// 获取分区的首选位置
final def preferredLocations(split: Partition): Seq[String] = {
checkpointRDD.map(_.getPreferredLocations(split)).getOrElse {
getPreferredLocations(split)
}
}
// 对应到每个分区的计算函数
def compute(split: Partition, context: TaskContext): Iterator[T]

主要就是围绕上面5个重要属性的一些操作

常用的函数/算子
// 返回仅包含满足过滤条件的元素的新RDD。
def filter(f: T => Boolean): RDD[T] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[T, T](
this,
(context, pid, iter) => iter.filter(cleanF),
preservesPartitioning = true)
}
// 通过将函数应用于此RDD的所有元素来返回新的RDD。
def map[U: ClassTag](f: T => U): RDD[U] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
}
// 首先向该RDD的所有元素应用函数,然后将结果展平,以返回新的RDD。
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.flatMap(cleanF))
}

我们可以发现几乎每个算子都会以当前RDD和对应的计算函数创建新的RDD,每个子RDD都持有父RDD的引用。

这就印证了RDD的不变性,也表明了RDD的计算是通过对RDD进行转换实现的。

案例

val words = Seq("hello spark", "hello scala", "hello java")
val rdd = sc.makeRDD(words)
rdd
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.foreach(println(_))

上面是一个简单的RDD的操作,我们先调用makeRDD创建了一个RDD,之后对rdd进行一顿算子调用。

首先调用flatMap,flatMap内部会以当前rdd和我们传入的_.split(" ")构建新的MapPartitionsRDD;

之后map,map以上步生成的MapPartitionsRDD和我们传入的(_, 1)构造新的MapPartitionsRDD;

之后reduceByKey,reduceByKey构造新的RDD;

走到foreach,foreach是行动操作,触发计算,输出。

小总结

  • RDD内部的计算除action算子以外,其他算子都是懒执行,不会触发计算,只是进行RDD的转换。
  • RDD的计算是基于分区为单位计算的,我们传进去的函数,作用于分区进行计算

转换、行动算子

从上面知道RDD是懒执行的,只有遇到行动算子才执行计算。

转换操作:在内部对根据父RDD创建新的RDD,不执行计算

行动操作:内部会调用sc.runJob,提交作业、划分阶段、执行作业。

一些常见的行动操作

foreach、foreachPartition、collect、reduce、count

除行动操作外,都是转换操作

宽、窄依赖

宽窄依赖是shuffle划分调度的重要依据。

先看看spark中与依赖有关的几个类(一层一层继承关系):

Dependency依赖的顶级父类
NarrowDependency 窄依赖
OneToOneDependency 表示父RDD和子RDD分区之间的一对一依赖关系的窄依赖
RangeDependency 表示父RDD和子RDD中分区范围之间的一对一依赖关系的窄依赖
ShuffleDependency 宽依赖

先说宽窄依赖的概念:

窄依赖:父RDD的每个分区只被一个子RDD分区使用

宽依赖:父RDD的每个分区都有可能被多个子RDD分区使用

其实就是父RDD的一个分区会被传到几个子RDD分区的区别。如果被传到一个子RDD分区,就可以不需要移动数据(移动计算);如果被传到多个子RDD分区,就需要进行数据的传输。

接下来看看Dependency内部的一些属性及方法:

// 依赖对应的rdd,其实就是当前rdd的父rdd。宽依赖和窄依赖都有这个属性
def rdd: RDD[T]
// 获取子分区对应的父分区(窄依赖的方法)
def getParents(partitionId: Int): Seq[Int] // 以下是宽依赖的属性及方法
// 对应键值RDD的分区器
val partitioner: Partitioner
// 在数据传输时的序列化方法
val serializer: Serializer = SparkEnv.get.serializer
// 键的排序方式
val keyOrdering: Option[Ordering[K]] = None
// 一组用于聚合数据的功能
val aggregator: Option[Aggregator[K, V, C]] = None
// 是否需要map端预聚合
val mapSideCombine: Boolean = false
// 当前宽依赖的id
val shuffleId: Int = _rdd.context.newShuffleId()
// 向管理员注册一个shuffle,并获取一个句柄,以将其传递给任务
val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle(
shuffleId, _rdd.partitions.length, this)
一些常见的宽窄依赖

窄依赖:map、filter、union、mapPartitions、join(当分区器是HashPartitioner)

宽依赖:sortByKey、join(分区器不是HashPartitioner时)

最后说一下reduceByKey,顺便说一下为什么当分区器HashPartitioner时就是窄依赖。

reduceByKey是用来将key分组后,执行我们传入的函数。

它是窄依赖,它内部默认会使用HashPartitioner分区。

同一个key进去HashPartitioner得到的分区id是一样的,这样进行计算前后同一个key得到的分区都一样,父RDD的分区就只被子RDD的一个分区依赖,就不需要移动数据。

所以join、reduceByKey在分区器是HashPartitioner时是窄依赖。

end. 个人理解,如有偏差,欢迎交流指正。

Reference

扶我起来,我还能学。




个人公众号:码农峰,定时推送行业资讯,持续发布原创技术文章,欢迎大家关注。

Spark RDD基本概念、宽窄依赖、转换行为操作的更多相关文章

  1. Spark RDD基本概念与基本用法

    1. 什么是RDD RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可并行计算的集合.RDD具 ...

  2. 【Spark-core学习之五】 RDD宽窄依赖 & Stage

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk1.8 scala-2.10.4(依赖jdk1.8) spark ...

  3. Spark RDD、DataFrame原理及操作详解

    RDD是什么? RDD (resilientdistributed dataset),指的是一个只读的,可分区的分布式数据集,这个数据集的全部或部分可以缓存在内存中,在多次计算间重用. RDD内部可以 ...

  4. spark RDD 常见操作

    fold 操作 区别 与 co 1.mapValus 2.flatMapValues 3.comineByKey 4.foldByKey 5.reduceByKey 6.groupByKey 7.so ...

  5. Spark RDD概念学习系列之Spark的算子的作用(十四)

    Spark的算子的作用 首先,关于spark算子的分类,详细见 http://www.cnblogs.com/zlslch/p/5723857.html 1.Transformation 变换/转换算 ...

  6. 关于spark RDD trans action算子、lineage、宽窄依赖详解

    这篇文章想从spark当初设计时为何提出RDD概念,相对于hadoop,RDD真的能给spark带来何等优势.之前本想开篇是想总体介绍spark,以及环境搭建过程,但个人感觉RDD更为重要 铺垫 在h ...

  7. Spark RDD 宽窄依赖

    RDD 宽窄依赖 RDD之间有一系列的依赖关系, 可分为窄依赖和宽依赖 窄依赖 从 RDD 的 parition 角度来看 父 RRD 的 parition 和 子 RDD 的 parition 之间 ...

  8. 【原】Learning Spark (Python版) 学习笔记(一)----RDD 基本概念与命令

    <Learning Spark>这本书算是Spark入门的必读书了,中文版是<Spark快速大数据分析>,不过豆瓣书评很有意思的是,英文原版评分7.4,评论都说入门而已深入不足 ...

  9. Spark RDD的依赖解读

    在Spark中, RDD是有依赖关系的,这种依赖关系有两种类型 窄依赖(Narrow Dependency) 宽依赖(Wide Dependency) 以下图说明RDD的窄依赖和宽依赖 窄依赖 窄依赖 ...

随机推荐

  1. Go和Java的性能对比,真的如此吗?

    前两天我看到了一篇文章,测试Java和Go和Python的性能,其测试内容是一个排序,排序一亿次,然后看那个语言耗时最短,我先贴一下这个文章的测试结果,Java竟然比Go快了一倍不止,Go不是号称接近 ...

  2. ubuntu pycharm、idea创建快捷方式

    编辑/usr/share/application/pycharm.desktop [Desktop Entry] Type=Application Name=Pycharm GenericName=P ...

  3. 树上主席树 - 查询树链上第K大

    Description 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始 ...

  4. JVM中的GC算法,JVM参数,垃圾收集器分类

    一.在JVM中什么是垃圾?如何判断一个对象是否可被回收?哪些对象可以作为GC Roots的根 垃圾就是在内存中已经不再被使用到的空间就是垃圾. 1.引用计数法: 内部使用一个计数器,当有对象被引用+1 ...

  5. Docker学习(十一)Docker系列结束-新的开始K8S

    Docker学习(十一)Docker系列结束-新的开始K8S 标签(空格分隔): docke k8s Docker系列结束 上一篇讲到使用docker官方提供的容器编排工具docker-compose ...

  6. springmvc 简化Javaweb 简单应用

    第一步 : 导入外部jar包,放在如图目录下 第二步:简单配置web.xml 文件 <?xml version="1.0" encoding="UTF-8" ...

  7. MySql主要性能指标说明

    在项目当中数据库一般都会成为主要的性能与负载瓶颈,那么针对数据库各项性能指标的监控与对应的优化是开发与运维人员需要面对的主要工作,而且这部分的工作会贯穿项目从开发到运行的整个周期里. 这篇文章中我们对 ...

  8. 博客与微信小程序的同步

    在此之前,先说说自己最近的打算,才购买了阿里云的服务器,想做一个网站和图床网盘之类的方便自己使用. 考虑到小程序,又打算将自己的博客内容放到小程序中.从零开发实属困难,应该还要一段时间才能完成. 目前 ...

  9. sqlserver 分页模糊查询

       积少成多 ----  仅以此致敬和我一样在慢慢前进的人儿 问题: 在sqlserver 进行模糊查询,出现问题 最初使用“concat”,进行拼串操作,如下所示: <select id = ...

  10. CUDA学习(二)之使用clock()函数

    clock()函数是C/C++中的计时函数,相关的数据类型是clock_t,使用clock函数可以计算运行某一段程序所需的时间,如下所示程序计算从10000000逐渐减一直到0所需的时间. #incl ...