Spark Transformation(转换)算子和 Action(行动)算子的区别是什么?
在 Apache Spark 中,Transformation(转换)算子和 Action(行动)算子是 RDD(弹性分布式数据集)操作的两大核心类别。
它们最本质的区别在于:Transformation 是“惰性”(Lazy)的,只记录逻辑不执行;而 Action 是“触发器”,它会真正启动计算任务。
以下是详细的对比分析:
1. 核心定义与行为区别
| 特性 | Transformation (转换算子) | Action (行动算子) |
|---|---|---|
| 返回值 | 返回一个新的 RDD。 | 返回 非 RDD 的结果(如 Int, Array, Map)或无返回值(写入文件)。 |
| 执行模式 | 惰性求值 (Lazy Evaluation)。不会立即触发计算。 | 立即执行。触发 Spark 提交 Job 并开始计算。 |
| 作用 | 构建逻辑执行计划(DAG/血统)。 | 触发物理执行,将结果返回给 Driver 或写入外部存储。 |
| 数量关系 | 一个 Spark 应用中可以有无限个 Transformation。 | 一个 Action 对应一个 Job。没有 Action,Spark 程序什么都不做。 |
2. 详细原理解析
Transformation (转换算子)
- 功能:将一个已存在的 RDD 转换成一个新的 RDD。
- 原理:当你调用
map或filter时,Spark 并没有真正去处理数据。它只是在内部记录下了一个操作步骤(构建 DAG 有向无环图),记住了“如果将来要计算数据,需要先做 A,再做 B”。这被称为建立 RDD 的 Lineage(血统)。 - 分类:
- Narrow (窄依赖):父 RDD 的一个分区只被子 RDD 的一个分区使用(无 Shuffle)。例如:
map,filter,flatMap. - Wide (宽依赖):父 RDD 的一个分区被子 RDD 的多个分区使用(涉及 Shuffle)。例如:
groupByKey,reduceByKey,join.
- Narrow (窄依赖):父 RDD 的一个分区只被子 RDD 的一个分区使用(无 Shuffle)。例如:
Action (行动算子)
- 功能:触发计算,处理数据,并产生最终结果。
- 原理:当代码运行到 Action 算子时,Spark Context 会查看之前积累的所有 Transformation 逻辑(DAG),制定物理执行计划,向集群提交一个 Job,真正开始读取数据、计算并输出。
- 数据流向:结果数据要么被拉取回 Driver 端(如
collect),要么被写入 外部存储系统(如 HDFS, S3)。
3. 常见算子举例
常用的 Transformation
map(func): 元素一对一映射。filter(func): 过滤数据。flatMap(func): 一对多映射。groupByKey(): 按 Key 分组。reduceByKey(func): 按 Key 聚合。union(otherRDD): 合并两个 RDD。distinct(): 去重。
常用的 Action
collect(): 将所有数据拉取回 Driver(注意内存溢出风险)。count(): 返回数据集的元素个数。first(): 返回第一个元素。take(n): 返回前 n 个元素。saveAsTextFile(path): 保存到文件系统。foreach(func): 对每个元素执行操作(通常用于写入数据库)。reduce(func): 聚合所有元素返回一个值(注意与reduceByKey的区别)。
4. 为什么要设计成“惰性求值”?
Spark 之所以将 Transformation 设计为 Lazy,主要是为了 性能优化:
- 流水线优化 (Pipelining):如果 Spark 立即执行每一步,那么
map之后可能需要把中间结果写内存,然后filter再读出来。通过惰性求值,Spark 可以将多个窄依赖操作(如 map + filter)合并在同一个 Stage 中,数据读进来后直接处理并过滤,减少内存/磁盘 IO。 - 只计算需要的数据:如果你写了
rdd.map(...).filter(...).take(1),因为 Action 是take(1),Spark 知道只需要一个结果,它可能在找到第一个符合条件的数据后就停止计算整个数据集,而不是处理完几 TB 数据后再给你第一条。
5. 生活类比
想象你在餐厅点餐:
Transformation (写菜单):
- 你说:“我要一份牛排”(
rdd1) - 你说:“牛排要七分熟”(
rdd2 = rdd1.map) - 你说:“再加点黑胡椒”(
rdd3 = rdd2.map) - 此时厨师并没有开始做菜,服务员只是在纸上记下了你的要求。
- 你说:“我要一份牛排”(
Action (下单/上菜):
- 你说:“好了,下单吧/上菜吧”(
rdd3.collect()) - 此时厨师才真正开始切肉、煎肉、撒调料,最后把成品端给你。
- 你说:“好了,下单吧/上菜吧”(
总结
- Transformation = 定义逻辑(返回 RDD,不干活)。
- Action = 执行逻辑(返回结果,开始干活)。
右滑查看面试常问