Spark Core 核心原理:从 RDD 到分布式计算

Spark Core 是整个 Spark 生态的基石,SQL、Streaming、MLlib、GraphX 都构建在它之上。本篇把 Spark Core 的核心概念串成一条线:为什么需要 Spark → RDD 是什么 → 怎么算 → 怎么容错 → 集群怎么跑起来。一天搞定核心原理。


一、为什么需要 Spark

MapReduce 是第一代分布式计算框架,但有两个硬伤:

  1. 计算结果必须落盘:Map 输出写本地磁盘,Reduce 再读,I/O 成瓶颈;
  2. 不适合迭代计算:机器学习、图计算要反复迭代,每次都读写磁盘,慢得无法接受。

Spark 的解法:

  • 把数据放在内存中迭代计算;
  • 用 RDD(Resilient Distributed Dataset,弹性分布式数据集) 作为统一数据抽象;
  • 用血统(Lineage) 而非副本实现容错。

Spark 生态:

复制代码
Spark Core(RDD、调度、内存管理)
   ├── Spark SQL        结构化数据处理
   ├── Spark Streaming  流式计算
   ├── MLlib            机器学习
   └── GraphX           图计算

二、RDD:Spark 的核心数据抽象

RDD 是一个不可变的、分区的、容错的分布式数据集。

RDD 的五大核心属性

复制代码
1. partitions          分区列表:数据被切成多少份
2. compute(p, split)   计算函数:每个分区怎么算
3. dependencies        依赖列表:由哪些父 RDD 转换而来
4. partitioner         分区器:可选,按什么规则分区
5. preferredLocations  首选位置:可选,每个分区在哪个节点算最快

关键点:RDD 不存数据,只存"怎么算"的描述。真正执行时才从父 RDD 拉数据计算。

RDD 的"弹性"体现在哪

  1. 自动重算:分区丢失时根据血统重算,不需要副本;
  2. 自动伸缩 :分区数可动态调整(repartition、coalesce);
  3. 内存不足溢写:内存不够时自动溢写到磁盘。

三、RDD 的创建与算子

创建方式

scala 复制代码
// 1. 从集合创建
val rdd1 = sc.parallelize(1 to 100, numSlices = 8)

// 2. 从外部存储创建
val rdd2 = sc.textFile("hdfs://namenode:8020/data.txt")

// 3. 从其他 RDD 转换
val rdd3 = rdd2.flatMap(_.split(" "))

算子分两类

类型 特点 常见算子
Transformation(转换) 返回新 RDD,惰性执行 map、flatMap、filter、reduceByKey、groupByKey、join、distinct
Action(行动) 触发计算,返回结果 collect、count、first、take、reduce、foreach、saveAsTextFile

关键规则:只有遇到 Action 才真正执行计算。 之前的 Transformation 只是记录计算逻辑。


四、惰性执行与依赖

惰性执行(Lazy Evaluation)

scala 复制代码
val rdd1 = sc.textFile("data.txt")     // 不执行
val rdd2 = rdd1.flatMap(_.split(" "))   // 不执行
val rdd3 = rdd2.map((_, 1))             // 不执行
val rdd4 = rdd3.reduceByKey(_ + _)      // 不执行
rdd4.collect()                          // 触发执行!

好处:可以全局优化(如合并多个 map)、避免中间结果落盘、按需计算。

窄依赖 vs 宽依赖

这是 RDD 最核心的概念,决定了 Stage 的切分。

类型 定义 特点 例子
窄依赖 父分区最多被一个子分区依赖 无需 Shuffle,可流水线执行 map、filter、union
宽依赖 父分区被多个子分区依赖 需要 Shuffle,必须等父 Stage 完成 groupByKey、reduceByKey、join、distinct

Shuffle 原理

宽依赖触发 Shuffle,分两阶段:

  1. Shuffle Write:Map 端按分区器把数据写到本地磁盘的多个 bucket 文件;
  2. Shuffle Read:Reduce 端从各 Map 节点拉取属于自己的 bucket,合并后聚合。

Shuffle 涉及磁盘 I/O、网络传输、序列化,是性能瓶颈。优化方向:能不 Shuffle 就不 Shuffle,必须 Shuffle 时合理设置分区数。


五、容错机制:血统 + 缓存

血统(Lineage)

RDD 记录自己由哪些父 RDD、经过什么算子转换而来。分区丢失时,Spark 根据血统从最近的父 RDD 重算丢失的分区,不需要重算全部。

Cache / Persist

被多次使用的 RDD(如迭代计算)应该缓存,避免每次重算。

scala 复制代码
val rdd = sc.textFile("data.txt").flatMap(_.split(" ")).cache()
rdd.count()     // 第一次:计算并缓存
rdd.collect()   // 第二次:直接读缓存

存储级别:

级别 说明
MEMORY_ONLY 只存内存(默认)
MEMORY_AND_DISK 内存不够溢写磁盘
MEMORY_ONLY_SER 序列化存内存,省空间
*_2 存两份副本

经验 :只缓存多次使用的 RDD;优先 MEMORY_ONLY;用完 unpersist() 释放。

Checkpoint

血统太长时重算代价大,用 Checkpoint 把 RDD 写到 HDFS,切断血统。

scala 复制代码
sc.setCheckpointDir("hdfs://namenode:8020/ck")
rdd.checkpoint()
rdd.count()   // 触发 checkpoint

六、运行架构

Spark 采用 Master-Worker 架构,核心角色:

角色 职责
Driver 运行 main,创建 SparkContext,构建 DAG,切分 Stage,调度 Task
Master 管理 Worker,为 Application 分配资源(Standalone 模式)
Worker 管理本节点资源,启动 Executor,向 Master 汇报
Executor 执行 Task,管理内存和磁盘,缓存 RDD

核心概念层级

复制代码
Application(一次 spark-submit)
   └── Job(一个 Action 触发一个)
        └── Stage(按宽依赖切分)
             └── Task(一个分区对应一个,最小执行单元)

Task 数 = 分区数,同一 Stage 内的 Task 并行执行。

DAG 调度

  1. DAGScheduler:把 Job 按宽依赖切分成多个 Stage,生成 TaskSet 提交给 TaskScheduler;
  2. TaskScheduler :把 Task 按数据本地性分配给 Executor 执行;
  3. 数据本地性级别 :PROCESS_LOCAL > NODE_LOCAL > RACK_LOCAL > ANY,优先调度到离数据最近的节点。

部署模式

模式 Driver 位置 适用场景
Local 本地 JVM 开发调试
Standalone 提交端 Spark 专用集群
YARN Client 提交端 调试、需看控制台
YARN Cluster AM 内部(集群内) 生产环境推荐
Kubernetes Pod 内 容器化环境

七、任务提交流程(YARN Cluster 模式)

复制代码
1. spark-submit 向 YARN RM 提交应用
2. RM 在某个 NM 上启动 ApplicationMaster(AM)
3. AM 内部启动 Driver,运行 main,创建 SparkContext
4. Driver 向 RM 申请 Container 资源
5. NM 在 Container 中启动 Executor
6. Executor 向 Driver 注册
7. 遇到 Action → DAGScheduler 切 Stage → TaskScheduler 下发 Task
8. Executor 执行 Task,ShuffleMapTask 写磁盘,ResultTask 返回结果
9. Driver 回收结果,所有 Job 完成后 SparkContext.stop(),释放资源

八、Spark Core 核心参数

参数 默认值 说明
spark.executor.memory 1g 每个 Executor 内存
spark.executor.cores 1 每个 Executor 核数
spark.executor.instances - Executor 数量
spark.driver.memory 1g Driver 内存
spark.default.parallelism - RDD 默认并行度
spark.task.maxFailures 4 Task 最大失败次数
spark.speculation false 推测执行
spark.locality.wait 3s 数据本地性等待时间
spark.driver.maxResultSize 1g Driver 回收结果上限

九、典型坑点

坑点 1:map 里的代码不执行

map 是 Transformation,惰性执行。要用 foreach 才会触发。

坑点 2:groupByKey 导致 OOM

groupByKey 把同一 Key 的所有值拉到一个分区,数据量大时 OOM。优先用 reduceByKey(Map 端先聚合,减少 Shuffle 量)。

坑点 3:外部大变量导致每个 Task 序列化一份

用广播变量:

scala 复制代码
val bc = sc.broadcast(largeMap)
rdd.map(x => bc.value.get(x))

坑点 4:collect() 拉取海量数据导致 Driver OOM

数据量大时用 take(n)、saveAsTextFile 或 foreach,不要 collect 全量。

坑点 5:repartition 与 coalesce 混用

  • repartition:触发 Shuffle,可增可减分区;
  • coalesce:默认不 Shuffle,只能减分区。

减少分区优先用 coalesce。

坑点 6:YARN 上 Executor 被 kill

Container killed by YARN for exceeding memory limits:Executor 实际内存超过 executor.memory + memoryOverhead。调大 spark.yarn.executor.memoryOverhead 或减小 executor 内存。


十、本篇小结

Spark Core 的核心是这几条线:

  1. 数据抽象:RDD 不存数据,只存「分区 + 计算函数 + 依赖 + 分区器 + 首选位置」;
  2. 执行模型:Transformation 惰性记录,Action 触发计算,窄依赖流水线,宽依赖 Shuffle;
  3. 容错:血统重算 + Cache/Checkpoint 优化;
  4. 架构:Driver 调度,Executor 执行,Master/Worker 管资源;
  5. 调度:DAGScheduler 切 Stage,TaskScheduler 按数据本地性分配 Task;
  6. 流程:spark-submit → 启动 Driver → 申请 Executor → 切 Stage → 执行 Task → 回收结果。

把这些打通,后续看 SQL、MLlib 都是在 Core 之上的封装。

相关推荐
强哥聊算力1 小时前
《算力江湖·DeepSeek》第四集:DeepSeek如何改变中国算力的采购逻辑?
大数据·运维·人工智能·ai·gpu算力
l1t1 小时前
DeepSeek总结的DuckDB 2.0.0 中的隐藏亮点
大数据·数据库·duckdb
数据皮皮侠1 小时前
全国休闲农业与乡村旅游示范县政策DID(2010-2025)
大数据·人工智能·搜索引擎·数据挖掘·智慧城市·制造
keyipatience1 小时前
Git学习(3)合并冲突全解析:从原理到实战解决
大数据·elasticsearch·搜索引擎
Paper数据分析-经管学术1 小时前
2000-2025年国家级大数据综合试验区政策试点DID面板数据
大数据
Angeldream1232 小时前
第 9 章 医学影像项目实战
大数据·图像处理·人工智能·python·医学影像处理
wasee0012 小时前
三四线城市适合投资XR大空间吗?投入、客流与回本周期测算
大数据·人工智能
Raas1002 小时前
MAI Gateway选购攻略:开源AI网关有哪些?避坑指南与对比
大数据·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
段一凡-华北理工大学2 小时前
高炉能源系统智能化:热风炉、煤气与极致能效~系列文章03:热风炉烧炉与风温的决定因素
大数据·linux·人工智能·能源·热风炉·高炉能源优化