基于本文回答
0
评论

spark的rdd

RDD(Resilient Distributed Dataset,弹性分布式数据集)是 Apache Spark 最核心的概念和基石。理解了 RDD,就理解了 Spark 的核心工作原理。

下面为你系统、通俗地拆解什么是 RDD。


一、 什么是 RDD?

我们可以把 RDD 拆开来理解:

  1. Resilient(弹性)
    • 容错性:如果某个节点上的数据丢失了,Spark 可以根据它的“血统(Lineage)”自动重新计算出来,不需要像 MapReduce 那样把中间结果写到磁盘上。
    • 分区的弹性:分区数量可以动态调整。
    • 内存/磁盘切换的弹性:数据优先缓存在内存中,内存不足时会自动溢写到磁盘。
  2. Distributed(分布式)
    • 数据并不是存在一台机器上,而是切分成多个分区(Partitions),分布在集群中的多台机器(Executor)上进行并行计算。
  3. Dataset(数据集)
    • 一个只读的、装有任意类型对象的集合(在 Python 中可以是 list,在 Java/Scala 中可以是 JVM 对象)。

通俗比喻:RDD 就像是一条分布在多台卡车上的流水线货物。每辆卡车装载货物的一部分(分区),大家同时开工(分布式计算)。如果某辆卡车在半路抛锚了,后方可以通过“生产配方”(血统)立刻重新生产出一车一模一样的货物(弹性)。


二、 RDD 的五大核心属性

在 Spark 内部,每个 RDD 都有以下五个核心特征:

  1. 分区列表(A list of partitions):一个 RDD 被切分成多少块。分区是 Spark 并行计算的最小单位。
  2. 计算每个分区的函数(A function for computing each split):Spark 知道如何对每个分区的数据进行计算。
  3. 对其他 RDD 的依赖列表(A list of dependencies on other RDDs):记录这个 RDD 是由哪些父 RDD 转换来的(这就是 血统 Lineage,容错的关键)。
  4. 键值对 RDD 的分区器(A Partitioner for key-value RDDs,可选):决定数据如何分发(如 HashPartitioner 或 RangePartitioner)。
  5. 优先位置列表(A list of preferred locations,可选):记录每个分区最好放在哪台机器上计算(数据本地性:移动计算,而不移动数据,以减少网络传输)。

三、 RDD 的核心操作(算子)

RDD 的算子分为两大类:Transformation(转换算子)Action(行动算子)

1. Transformation(转换算子)

  • 特点惰性求值(Lazy Evaluation)。当你调用转换算子时,Spark 并没有真正开始计算,而只是记录下了这个操作(画了一张计算图/DAG)。
  • 作用:将一个 RDD 转换成另一个新的 RDD。
  • 常用算子
    • map():对每一条数据进行处理。
    • filter():过滤数据。
    • flatMap():扁平化映射(先 map 再压平)。
    • groupByKey() / reduceByKey():按 Key 进行分组或聚合。

2. Action(行动算子)

  • 特点触发计算。当遇到 Action 算子时,Spark 才会真正提交 Job,开始从头到尾执行之前所有的 Transformation。
  • 作用:获取计算结果,或者将结果保存到外部系统。
  • 常用算子
    • collect():将集群中的数据收集回 Driver 端(大数据量时慎用,容易 OOM)。
    • count():计算元素个数。
    • take(n):获取前 n 个元素。
    • saveAsTextFile(path):将数据保存到 HDFS 或本地文件。

四、 依赖关系:宽依赖 vs 窄依赖

这是 Spark 性能调优和理解 Stage 划分的关键:

  1. 窄依赖(Narrow Dependency)
    • 定义:父 RDD 的每个分区最多被子 RDD 的一个分区使用。
    • 例子map(), filter(), flatMap()
    • 特点:不需要网络传输,数据在本地节点直接计算,效率极高(Pipeline 管道化)。
  2. 宽依赖(Wide Dependency,又称 Shuffle)
    • 定义:父 RDD 的一个分区会被子 RDD 的多个分区使用。
    • 例子reduceByKey(), groupByKey(), join()
    • 特点:必须经过 Shuffle 阶段,数据需要通过网络在不同节点间传输、重组,非常消耗 I/O 和时间。

Stage(阶段)的划分:Spark 会以宽依赖为边界,把一个复杂的计算任务切分成多个 Stage。


五、 RDD 的代码示例 (PySpark)

经典的 WordCount(单词计数) 例子:

python
from pyspark import SparkContext

# 1. 创建 SparkContext
sc = SparkContext("local", "WordCountApp")

# 2. 读取文件,创建初始 RDD
text_rdd = sc.textFile("hdfs://path/to/hello.txt")

# 3. 转换操作 (Transformation)
# 扁平化:将每行文本按空格切分成单词
words_rdd = text_rdd.flatMap(lambda line: line.split(" "))
# 映射:将每个单词变成 (word, 1) 的键值对
word_pairs = words_rdd.map(lambda word: (word, 1))
# 聚合:按相同的 word,累加它们的次数(此步骤会发生 Shuffle/宽依赖)
word_counts = word_pairs.reduceByKey(lambda a, b: a + b)

# 4. 行动操作 (Action) - 此时才真正开始计算
# 收集结果并打印
result = word_counts.collect()
for word, count in result:
    print(f"{word}: {count}")

六、 RDD 的优缺点与演进

虽然 RDD 是 Spark 的基石,但在现代 Spark 开发中,我们很少直接写 RDD,而是写 DataFrameDataset

RDD 的缺点:

  1. 没有 Schema(结构信息):Spark 知道 RDD 里面是 Scala/Python 对象,但不知道对象内部有哪些字段。
  2. 优化空间小:因为没有结构信息,Spark 的优化器(Catalyst)无法像 SQL 那样帮你自动优化执行计划。
  3. 序列化开销大:尤其是在 PySpark 中,JVM 和 Python 进程之间的数据传输和序列化非常耗时。

现代 Spark 推荐:

  • DataFrame / Dataset:在 RDD 的基础上加上了 Schema(列名和类型)。
  • 底层关系:DataFrame 的底层依然是 RDD,但它通过 Spark SQL Catalyst 优化器Tungsten 内存管理器 进行了极大的优化。

总结:什么时候用 RDD?

  1. 你正在处理非结构化数据(如视频、音频流,或者未排版的纯文本)。
  2. 你需要非常底层的控制,比如精确控制数据的分区方式、自定义内存序列化方式。
  3. 你在维护老旧的 Spark 1.x 遗留系统。
  4. 否则,一律推荐使用 DataFrame / Dataset (Spark SQL) 进行开发。
右滑查看面试常问