RDD,学名可伸缩的分布式数据集(Resilient Distributed Dataset)。初次听闻,感觉很高深莫测。待理解其本质,却发现异常简洁优雅。本文试图对其进行一个快速侧写,试图将这种大数据处理中化繁为简的美感呈现给你。
RDD 是什么
- RDD 本质上是对数据集的某种抽象。
RDD 将数据集合进行三层组织:Dataset(数据集)- Partition(分片)- Record(单条记录)。三是一个很合适的层数,每层都有其着力点,多了显冗余,少了力不够。
举个生活中例子,高中某个班级(Dataset),我们把他们按列分成四个小组(Partition),每个小组有大概十来个同学(Record)。任何一群人来了,我们都可以以这种形式将其进行组织。同样,任何一个数据集,我们也可以按类似的三个层级进行划分。
- RDD 是基于内存的分布式的数据集。
单机资源总是有限的,RDD 生来就是为多机而设计的。将数据集划分为多个分片(Partition),就是为了能让一个数据集分散到不同机器上,从而利用多个机器的存储和计算资源,对数据进行并行处理。此外,分片还可以隔离故障阈,当某个机器故障后,只需要恢复该机器上对应分片即可,其他机器的分片不受影响。
相比 HDFS 或 GFS 基于外存,RDD 以内存为第一介质,以此可以显著降低计算延迟。当然,如果数据过多,也提供退化策略 —— 外溢(Spill)到外存。尤其对于一些重要的中间计算结果,多选择持久化到外存,以避免宕机时重新计算。
- RDD 是不可变(immutable)的。
数据集不能被原地( in-place) 的修改,即不能只修改集合中某个 Record。只能通过算子将一个数据集整体变换成另一个数据集。只要知道起始集,和一个确定的变换序列,就能得到一个唯一确定的结果集,因此常用此方法来进行容错(lineage)。如某些分区数据丢了,只需要重放其所经历的算子序列即可。
那么,不可变有什么好处呢?可以安全的并发。对于不可变数据,不用处理各种读写冲突,也不需要加锁。这是一种典型的 tradeoff,牺牲空间,换来更快的计算,更好的并发。
基于 RDD 进行数据处理
使用算子可以将一个 RDD 变换到另一个 RDD,也可以终结计算过程进行输出。通过合理组合这些算子,可以实现对数据集的复杂处理。
算子是一些基本运算过程的抽象,我们可以简单的理解为:
- 拓展版的 map 和 reduce。
- 弱化版的 sql 算子。
常见的算子包括:
各种常见算子
如上图,算子可以分为两种:
- 变换算子(transformations):作用于 RDD 生成新的 RDD。
- 终结算子(action):定义结束运算时如何输出。
执行流程
从整体上理解,基于 RDD 的整个处理流程可以拆解为三个步骤:
- 将数据集从外部导入系统,变成初始 RDD。
- 将数据处理逻辑转换成一系列算子的组合,先后施加到 RDD 上。
- 利用终结算子,结束运算,输出结果。
执行调度
RDD 的整个处理流程我们称为任务(Job),每个变换称为子任务(Task)。如果将 RDD 理解为点,施加算子进行变换的关系理解为边,则整个任务的执行过程可以构成一个有向无环图(DAG)。
为了逐步执行这个有向无环图,我们可以一步步来考虑:
- 最简单的,可以对该 DAG 进行拓扑排序,然后按顺序一个接一个的进行执行。
- 为了提高并发,可以识别 DAG 的依赖关系,对没有依赖的子任务可以进行并发执行。
- 为了进一步提高并发,参考 CPU 的流水线模式,可以按分区粒度对所有子任务进行流水线式的执行。
正如河流往往有汇聚点,即所谓瓶颈。在变换算子中,也有一些特殊算子,我们称之为 shuffle 算子(reduce、join、sort)。这种算子会将 RDD 的所有分区打散重排(所谓 shuffle),从而打断分区的流水化执行。于是 Spark 就以这种算子为界,将整个 Job 划分为多个 Stage,逐 Stage 进行调度。这样,在每个 Stage 内的子任务可以流水线的执行。通常,在 Stage 内子任务执行完后,我们会将其中间结果 Persist 到外存,以避免任何一台相关机器宕机,丢失某个分片,在 Stage 边界处造成所有分区全部重新执行。
Spark 划分执行过程
小结
在 RDD 的实现系统 Spark 中,对数据集进行一致性的抽象正是计算流水线(pipeline)得以存在和优化的精髓所在。依托 RDD,Spark 整个系统的基本抽象极为简洁:数据集+算子。理解了这两个基本元素的内涵,利用计算机的惯常实践,就可以自行推演其之后的调度优化和衍生概念(如分区方式、宽窄依赖)。
总结一下,RDD 承自 MapReduce 而来,常驻内存以优化 IO 开销、利用流水线调度以降低批处理延迟,使得在多机上交互式的执行处理成为可能。
更细节的,可以参考我之前翻译的这篇文章: Spark 理论基石 —— RDD