这是kk第003篇文章
本文共1428字,阅读约9分钟
一秒钟看完全文:RDD表示已被分区、不可变的,并能够被并行操作的数据集合。
背景
传统的 MapReduce 框架之所以运行速度缓慢,很重要的原因就是有向无环图的中间计算结果需要写入硬盘这样的稳定介质中来防止运行结果丢失。
而每次调用中间计算结果都需要进行一次硬盘的读取,反复对硬盘进行读写操作以及潜在的数据复制和序列号操作大大提高了计算的延迟。
因此,很多研究人员试图提出一个新的分布式存储方案,不仅保持之前系统的稳定、错误恢复和扩展性,还要尽可能减少磁盘 I/O 操作。
一个可行的设想就是在分布式内存中,存储中间计算结果,因为对内存的读写操作速度远快于硬盘。
而 RDD 就是一个基于分布式内存的数据抽象,它不仅仅支持基于工作集的应用,同时具有数据流模型的特点。
RDD 具有以下特性:分区、不可变和并行操作
分区
顾名思义,分区代表同一个 RDD 包含的数据被存储在系统的不同节点中,这也是它可以被并行处理的前提。
逻辑上,我们可以认为 RDD 是一个大的数组。数组中的每个元素代表一个分区 ( Partition)。
在物理存储中,每个分区指向一个存放在内存或者硬盘中的数据块(Block),而这些数据块是独立的,它们可以被存放在系统中的不同节点。
所以,RDD 只是抽象意义的数据集合,分区内部并不会存储具体的数据。下图就很好的展示了 RDD 的分区逻辑结构。
RDD 中的每个分区存有它在该 RDD 中的 index 。通过 RDD 的 ID 和分区的 index 可以唯一确定对应数据块的编号,从而通过底层存储层的接口中提取到数据进行处理。
在集群中,各个节点上的数据块会尽可能地存放在内存中,只有当内存没有空间时才会存入硬盘。这样可以最大化地减少硬盘读写的开销。
虽然 RDD 内部存储的数据是只读的,但是,我们可以去修改(例如通过repartition 转换操作),并行计算单元的划分结构,也就是分区的数量。
不可变性
不可变性代表每一个 RDD 都是只读的,它所包含的分区信息不可以被改变。既然已有的 RDD 不可以被改变,我们只可以对现有的 RDD 进行转换操作,得到新的 RDD 作为中间计算的结果。
从某种程度上来讲,RDD 与函数式编程的 Collection 很相似。
val conf = new SparkConf().setMaster("local[2]").setAppName("TextTest")
val sc = new SparkContext(conf)
val lines = sc.textFile("d://1.txt")
val lineLengths = lines.map(m => m.length)
val length = lineLengths.reduce(_+_)
print(length)
在上述的简单例子中,首先读入文本文件 data.txt,创建了第一个 RDD lines,它的每一个元素时一行文本。然后调用 map 函数去映射产生第二个 RDD lineLengths,每个元素代表每一行简单文本的字数。最后调用 reduce 函数去得到第三个 RDD totalLength,它只有一个元素,代表整个文本的总字数。
那么这样会带来什么好处呢?
显然,对于代表中间结果的 RDD,我们需要记录它是通过哪个 RDD 进行转换操作得来,即依赖关系,而不用立刻去具体存储计算出的数据本身。
这样作有助于提升 spark 的计算效率,并且使错误恢复更加容易。
试想,在 一个有N步的计算模型中,如果记载第 N 步输出 RDD 的节点发生故障,数据丢失,我们可以从第 N-1 步的 RDD 出发,再次计算,从无需重复整个 N 步的计算过程。这样的容错特性也是 RDD 为什么是一个 “弹性” 的数据集的原因之一。
并行操作
由于单个 RDD 的分区特性,使得它天然支持并行操作,即不同节点上的数据可以被分别处理,然后产生一个新的 RDD。
环境不会改变,解决之道在于改变自己。
与你共勉