Spark RDD
RDD(Resilient Distributed Dataset),即弹性分布式数据集,是Spark的基础数据结构,RDD具有不可修改的特性,并且会在集群的不同节点运行计算。Spark RDD里面的数据集会被逻辑分成若干个分区,这些分区是分布在集群的不同节点的,基于这样的特性,RDD才能在集群不同节点并行计算。
什么是Spark RDD
RDD是"Resilient Distributed Dataset"的缩写,从全称就可以了解到RDD的一些典型特性。
- Resilient(弹性):RDD之间会形成有向无环图(DAG),如果RDD丢失了或者失效了,可以从父RDD重新计算得到。即容错性。
- Distributed(分布式):RDD的数据是以逻辑分区的形式分布在集群的不同节点的。
- Dataset(数据集):即RDD存储的数据记录,可以从外部数据生成RDD,例如Json文件,CSV文件,文本文件,数据库等。
创建RDD的3种方式
- 外部存储
- 其他RDD
- 数据集合
Spark RDD可以被缓存和手动分区。如果同一个RDD需要使用多次,你可以把它缓存起来,后续使用的时候直接从内存读取,而不需要重新计算。如果要保证数据均衡分布在集群的节点,那么手动分区就是一种比较重要的方式。
要缓存RDD可以调用persist方法,Spark默认会把rdd存储在内存,如果内存不够,则会把rdd溢写到磁盘。
为什么需要RDD
Spark为什么要设计RDD这种数据结构。有以下几种主要原因:
- 迭代算法
- 交互式数据挖掘工具
- 分布式内存共享(Distributed Shared Memory,DSM)是一种非常通用的抽象,但这种抽象使得它很难在商业集群系统实现高效性和容错性。
- 在分布式计算系统中,数据是存储在类似HDFS、Amazon S3这种稳定的分布式存储系统里的。这会使得任务的计算变慢,因为在计算过程中,产生很多磁盘IO,数据拷贝和序列化等操作。
前面两点可以把数据存储在内存,这可以显著提升数据运算性能。为了达到高效容错,RDD提供了基于粗粒度转换操作的函数,例如map、filter等。
Spark RDD 和 DSM(Distributed Shared Memory)
RDD和DSM二者具有很大的区别。主要从以下6个方面来详细比较:
读写操作
RDD - RDD的读操作有粗粒度和细粒度两种,粗粒度操作针对的是RDD的整个数据集,相反,细粒度操作针对的是RDD数据集的个别元素。而写操作是粗粒度操作,即写的时候是整个数据集一起写,而不是只写其中的某个元素。
DSM - DSM的读写操作都是细粒度操作。
一致性
RDD - 一致性对于RDD来说没那么重要,因为它具有不可修改的特性,换句话说RDD是只读的。
DSM - DSM是强一致性的,如果开发者遵循开发协议,那么系统会保证数据的一致性,计算结果都是可预期的。
故障恢复机制
RDD - 如果RDD数据出现丢失情况,Spark RDD通过DAG很容易就可以从父RDD把丢失的数据重新计算出来。每一次进行转换操作生成的新RDD都是不可修改的,所以很容易对它进行重算并恢复数据。
DSM - DSM利用检查点技术达到数据恢复的效果,应用程序通过回滚到最近的检查点而不重新计算来达到数据恢复效果。
掉队问题缓解
有些节点的运算速度远远比其他节点慢,完成任务需要消耗更多的时间。发生这种情况的原因可能是负载不均衡,IO频繁,垃圾回收等等。
RDD - RDD通过备份task,即把task移到其他节点运行,来解决任务掉队问题。
DSM - 彻底解决掉队问题对于DSM来说比较困难。
内存不足的表现
- 如果没有足够的内存存储RDD,那么RDD会把数据转移到磁盘。
- 如果内存不够用,将会严重影响DSM的计算性能。它并不会把数据转移到磁盘。
Spark RDD特性
RDD有几个比较重要的特性,正是因为这些特性,让Spark脱颖而出, 成为离线和在线计算领域中不可或缺的大数据计算引擎。
内存计算
Spark RDD运算数据是在内存中进行的,在内存足够的情况下,不会把中间结果存储在磁盘,所以计算速度非常高效。
惰性求值
所有的转换操作都是惰性的,也就是说不会立即执行任务,只是把对数据的转换操作记录下来而已。只有碰到action操作需要返回数据给驱动程序(driver program)的时候,他们才会被真正的执行。
容错性
Spark RDD具备容错特性,在RDD失效或者数据丢失的时候,可以根据DAG从父RDD重新把数据集计算出来,以达到数据容错的效果。
不变性
RDD是进程安全的,因为RDD是不可修改的。它可以在任何时间点被创建和查询,使得缓存,共享,备份都非常简单。在计算过程中,是RDD的不可修改特性保证了数据的一致性。
分区
分区是Spark RDD并行计算的基础。每个分区是对数据集的逻辑划分。可以对已存在的分区做某些转换操作创建新分区。
持久化
可以调用cache或者persist函数,把RDD缓存在内存、磁盘,下次使用的时候不需要重新计算而是直接使用。
粗粒度操作
通过使用map、filter、groupby等操作对RDD数据集进行集体操作。而不是只操作其中某些数据集元素。
数据本地化
Spark会把计算程序调度到尽可能离数据近的地方运行,即移动计算而不是移动数据。
Spark RDD操作
RDD支持两种操作:
- 转换操作(Transformation)
- 行动操作(Actions)
转换操作(Transformation)
转换操作以RDD做为输入参数,然后输出一个或者多个RDD。转换操作不会修改输入RDD。Map()
、Filter()
这些都属于转换操作。
转换操作是惰性求值操作,只有在碰到行动操作的时候,转换操作才会真正实行。某些操作支持管道化,这是一种比较有效的性能优化手段。
转换操作分两种:窄转换 和宽转换。
窄依赖(Narrow Dependencies)
所谓窄依赖,就是类似map、filter这种转换操作,他们输出的分区结果只会来源于父RDD的其中一个分区。父RDD和子RDD partition之间的关系是一对一的。或者父RDD一个partition只对应一个子RDD的partition情况下的父RDD和子RDD partition关系是多对一的。不会有shuffle的产生。父RDD的一个分区只会到子RDD的一个分区。
宽依赖(Wide Dependencies)
父RDD与子RDD partition之间的关系是一对多。会有shuffle的产生。父RDD的一个分区的数据会到子RDD的不同分区里面。比如groupByKey()
、reduceByKey()
等函数。
行动操作(Action)
Action是数据执行部分,其通过执行count,reduce,collect等方法真正执行数据的计算部分。实际上,RDD中所有的操作都是Lazy模式进行,运行在编译中不会立即计算最终结果,而是记住所有操作步骤和方法,只有显式的遇到启动命令才执行。这样做的好处在于大部分前期工作在Transformation时已经完成,当Action工作时,只需要利用全部自由完成业务的核心工作。
Spark RDD的局限性
虽然RDD数据结构有很多优势,但是它还是存在一些局限性的。
没有内置优化引擎
在处理结构化数据的时候,RDD并不能发挥Spark的高级优化器,比如catalyst优化器、钨丝执行引擎。开发者必须基于RDD的特征具体做优化。
处理结构化数据
RDD不能像DataFrame和数据集推断出数据的模型,必须开发者来指定。
性能局限性
作为内存里的JVM对象,随着数据量的增长,垃圾回收和Java序列化性能会越来越低,RDD的运算性能也会随之降低。
存储局限性
如果没有足够的内存存储RDD,Spark会把RDD溢写到磁盘,这样会导致计算性能低下。