一文理解 Spark 的基石 RDD

1,041 阅读5分钟

RDD,学名可伸缩的分布式数据集(Resilient Distributed Dataset)。初次听闻,感觉很高深莫测。待理解其本质,却发现异常简洁优雅。本文试图对其进行一个快速侧写,试图将这种大数据处理中化繁为简的美感呈现给你。

RDD 是什么

  1. RDD 本质上是对数据集的某种抽象

RDD 将数据集合进行三层组织:Dataset(数据集)- Partition(分片)- Record(单条记录)。三是一个很合适的层数,每层都有其着力点,多了显冗余,少了力不够。

举个生活中例子,高中某个班级(Dataset),我们把他们分成按列分成四个小组(Partition),每个小组有大概十来个同学(Record)。任何一群人来了,我们都可以以这种形式将其进行组织。同样,任何一个数据集,我们也可以按类似的三个层级进行划分。

  1. RDD 是基于内存分布式的数据集。

单机资源总是有限的,RDD 生来就是为多机而设计的。将数据集划分为多个分片(Partition),就是为了能让一个数据集分散到不同机器上,从而利用多个机器的存储和计算资源,对数据进行并行处理。此外,分片还可以隔离故障阈,当某个机器故障后,只需要恢复该机器上对应分片即可,其他机器的分片不受影响。

相比 HDFS 或 GFS 基于外存,RDD 以内存为第一介质,以此可以显著降低计算延迟。当然,如果数据过多,也提供退化策略 —— 外溢(Spill)到外存。尤其对于一些重要的中间计算结果,多选择持久化到外存,以避免宕机时重新计算。

  1. RDD 是不可变(immutable)的。

数据集不能被原地( in-place) 的修改,即不能只修改集合中某个 Record。只能通过算子将一个数据集整体变换成另一个数据集。只要知道起始集,和一个确定的变换序列,就能得到一个唯一确定的结果集,因此常用此方法来进行容错(lineage)。如某些分区数据丢了,只需要重放其所经历的算子序列即可。

那么,不可变有什么好处呢?可以安全的并发。对于不可变数据,不用处理各种读写冲突,也不需要加锁。这是一种典型的 tradeoff,牺牲空间,换来更快的计算,更好的并发。

基于 RDD 进行数据处理

使用算子可以将一个 RDD 变换到另一个 RDD,也可以终结计算过程进行输出。通过合理组合这些算子,可以实现对数据集的复杂处理。

算子是一些基本运算过程的抽象,我们可以简单的理解为:

  1. 拓展版的 map 和 reduce。
  2. 弱化版的 sql 算子。

常见的算子包括:

各种常见算子

如上图,算子可以分为两种:

  1. 变换算子(transformations):作用于 RDD 生成新的 RDD。
  2. 终结算子(action):定义结束运算时如何输出。

执行流程

从整体上理解,基于 RDD 的整个处理流程可以拆解为三个步骤:

  1. 将数据集从外部导入系统,变成初始 RDD。
  2. 将数据处理逻辑转换成一系列算子的组合,先后施加到 RDD 上。
  3. 利用终结算子,结束运算,输出结果。

执行调度

RDD 的整个处理流程我们称为任务(Job),每个变换称为子任务(Task)。如果将 RDD 理解为点,施加算子进行变换的关系理解为边,则整个任务的执行过程可以构成一个有向无环图(DAG)。

为了逐步执行这个有向无环图,我们可以一步步来考虑:

  1. 最简单的,可以对该 DAG 进行拓扑排序,然后按顺序一个接一个的进行执行。
  2. 为了提高并发,可以识别 DAG 的依赖关系,对没有依赖的子任务可以进行并发执行。
  3. 为了进一步提高并发,参考 CPU 的流水线模式,可以按分区粒度对所有子任务进行流水线式的执行。

正如河流往往有汇聚点,即所谓瓶颈。在变换算子中,也有一些特殊算子,我们称之为 shuffle 算子(reduce、join、sort)。这种算子会将 RDD 的所有分区打散重排(所谓 shuffle),从而打断分区的流水化执行。于是 Spark 就以这种算子为界,将整个 Job 划分为多个 Stage,逐 Stage 进行调度。这样,在每个 Stage 内的子任务可以流水线的执行。通常,在 Stage 内子任务执行完后,我们会将其中间结果 Persist 到外存,以避免任何一台相关机器宕机,丢失某个分片,在 Stage 边界处造成所有分区全部重新执行。

Spark 划分执行过程

小结

在 RDD 的实现系统 Spark 中,对数据集进行一致性的抽象正是计算流水线(pipeline)得以存在优化的精髓所在。依托 RDD,Spark 整个系统的基本抽象极为简洁:数据集+算子。理解了这两个基本元素的内涵,利用计算机的惯常实践,就可以自行推演其之后的调度优化和衍生概念(如分区方式、宽窄依赖)。

总结一下,RDD 承自 MapReduce 而来,常驻内存以优化 IO 开销、利用流水线调度以降低批处理延迟,使得在多机上交互式的执行处理称为可能。

更细节的,可以参考我之前翻译的这篇文章:

www.qtmuniao.com/2019/11/14/…


我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿