🎓 总站 🏠 本课目录 01 概论 02 虚拟化 03 云原生 04 K8s基础 05 K8s进阶 06 消息队列 07 分布式存储 08 分布式文件系统 09 并行编程 10 Spark
云计算技术 · 第10讲

Spark 大数据处理

从 Spark 内存计算与 DAG 引擎,到运行架构、RDD 五大特性、Transformation/Action 算子、惰性求值、血缘容错,再到 Spark SQL 与 Structured Streaming。

📚 学习进度
0%

🎯学习目标

  • 理解 Spark 内存计算相比 MapReduce 磁盘计算的优势与适用场景;
  • 掌握 DAG 执行引擎、Stage 划分与宽窄依赖的关系;
  • 掌握 Spark 运行架构(Driver/Executor/Cluster Manager/SparkSession);
  • 掌握 RDD 五大特性、Transformation/Action 算子与惰性求值;
  • 理解血缘(Lineage)容错与缓存策略,了解 Spark SQL(Catalyst)与 Structured Streaming。

1Spark 简介与内存计算

Hadoop MapReduce 推动了离线大数据处理,但其执行模型偏"批处理+磁盘中转"。对日志清洗这类单次扫描够用,但对机器学习、交互式查询、图计算等迭代优化任务,中间结果反复落盘非常慢。

💡 Spark 核心思想① 尽量把中间结果保留在内存,减少磁盘 I/O;② DAG 执行引擎;③ 统一批处理与流处理。
维度MapReduceSpark
中间结果通常落本地磁盘/HDFS尽量保留内存,必要时再落盘
执行模型固定 Map→Shuffle→ReduceDAG + 多算子流水
迭代算法每轮重复读写磁盘可复用缓存数据,迭代高效
交互式查询延迟较高响应更快
典型任务大规模离线批处理批处理、SQL、机器学习、流处理

Spark 适用场景

场景是否适合原因
海量离线 ETL✅ 适合并行处理强、生态成熟
交互式 SQL 分析✅ 适合DataFrame + Catalyst 性能好
迭代机器学习✅ 适合缓存与迭代计算优势明显
毫秒级事务处理❌ 不适合Spark 不是 OLTP 数据库
超小规模数据脚本未必需要本地 Python/Pandas 可能更轻量

2DAG 执行引擎 ⭐(核心考点)

Spark 把一连串 Transformation 看成一个有向无环图(DAG):节点是数据集/计算结果,边表示依赖关系。执行流程:先有逻辑 DAG,再拆成物理执行 Stage

⭐ Stage 划分依据:Shuffle 边界Spark 依据 Shuffle 边界(宽依赖)把 DAG 切成多个 Stage,Stage 内部尽量流水执行。好处:系统能观察全局依赖,不只盯着单个 Map 或 Reduce,从而做整体优化。

宽依赖 vs 窄依赖

➡️

窄依赖

Narrow
子分区只依赖少量父分区,适合流水执行。如 map、filter,无 Shuffle
🔀

宽依赖

Wide
父 RDD 一个分区对应子 RDD 多个分区,可能产生 Shuffle如 reduceByKey、join、groupByKey
窄依赖(流水) 宽依赖(Shuffle)
图1 · 窄依赖一对一可流水;宽依赖一对多产生 Shuffle,是 Stage 划分边界
Stage 1(窄依赖流水)textFile → flatMap → map Stage 2(聚合)reduceByKey → collect Shuffle
图2 · WordCount 的 DAG 按 reduceByKey 的 Shuffle 边界切分为两个 Stage

3Spark 运行架构

Spark 运行架构包括:集群资源管理器(Cluster Manager)、工作节点(Worker Node)、任务控制节点(Driver)、负责执行的进程(Executor)。

🧠 Driver Programmain + SparkSession + 调度 Cluster Manager Executor 1 Executor 2 申请资源 下发 Task
图3 · Spark 架构:Driver 控制、Cluster Manager 调资源、Executor 执行 Task
组件职责
Driver应用控制中心:运行 main、创建 SparkSession、解析算子;Action 出现时转换成 Job/Stage/Task 并申请资源;维护元信息
Executor运行在工作节点上的进程,接收 Driver 下发的 Task 执行,管理本地 Cache 与 Shuffle 数据;长期驻留减少进程启停开销
Cluster Manager负责集群资源管理与节点生命周期,与计算引擎解耦
💡 Executor 两大优点相比 MapReduce:① 利用多线程执行任务,减少启动开销;② 内置 BlockManager 存储模块,将内存和磁盘共同作为存储,减少 IO 开销。

Cluster Manager 类型与 SparkSession

类型适用场景
Standalone教学、小型集群、快速实验(Spark 自带)
YARN已有 HDFS/Hadoop 基础设施的企业
Mesos多框架共享集群
Kubernetes现代云环境与容器平台(云原生)

SparkSession 统一了早期分裂的 SparkContext/SQLContext/HiveContext 等入口,整合 RDD、DataFrame、SQL 能力,通过 SparkSession.builder 配置 appName、master 等参数,让 API 更一致。

4RDD 五大特性

RDD(Resilient Distributed Dataset,弹性分布式数据集)是 Spark 最基础的数据抽象。用户只需将逻辑表达为一系列转换处理,不必担心底层分布式特性。不同 RDD 间的转换形成依赖关系,可管道化、避免中间数据存储。

特性含义作用
Partitions(分区)分区集合决定并行度与任务划分
Dependencies(依赖)与父 RDD 的依赖决定 Stage 与容错恢复
Compute Function(计算函数)每个分区如何计算定义转换逻辑
Partitioner(分区器)键值数据如何分区优化聚合与连接
Preferred Locations(首选位置)分区的首选计算位置提高数据本地性
📖 关键细节分区:每个分区通常对应一个 Task,分区过少并行不足、过多调度开销大。Partitioner:Pair RDD 可用 HashPartitioner 或 RangePartitioner,相同分区器可减少 join/aggregate 的 Shuffle。Preferred Locations:Task 尽量调度到数据所在节点(搬计算比搬数据便宜)。RDD 不可变:每次 Transformation 返回新 RDD,血缘链更清晰。

5算子与惰性执行 ⭐(核心考点)

类别代表操作特点
Transformationmap、filter、flatMap、reduceByKey、groupByKey、distinct返回新 RDD,通常惰性执行
Actioncollect、count、take、first、saveAsTextFile触发真正计算
⭐ 惰性执行(Lazy Evaluation)Transformation 默认不会立即运行,只构建一条逻辑计算链;Spark 等到 Action 出现后才统一执行。好处:减少无意义中间结果、给优化器留出全局优化空间——这是 Spark 能做 DAG 优化与 Stage 切分的前提。
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("RDDWordCount").master("local[*]").getOrCreate()
sc = spark.sparkContext
lines = sc.textFile("data/words.txt")
words = lines.flatMap(lambda line: line.split())   # Transformation(惰性)
pairs = words.map(lambda w: (w.lower(), 1))         # Transformation(惰性)
counts = pairs.reduceByKey(lambda a, b: a + b)      # Transformation(宽依赖)
print(counts.collect())                             # Action → 触发执行
spark.stop()
💡 reduceByKey vs groupByKeyreduceByKey 先局部聚合再全局聚合(Map 端预合并),大幅减少 Shuffle 传输,适合大规模聚合;groupByKey 把所有值搬到 Reduce 端再处理,代价较高。

6血缘容错与缓存策略

血缘追踪(Lineage)

⭐ Lineage 容错机制Spark 为 RDD 记录 Lineage(血缘),而不是对每个中间结果都复制。一旦某个分区丢失,就沿着血缘链重新计算该分区——从"全量复制"变为"按需重算"。如果某些数据重算代价太高,可通过 Checkpoint 持久化提升恢复稳定性。

缓存策略:Cache 和 Persist

方法说明
cache()≈ persist(MEMORY_ONLY),只存内存
persist()支持更多存储级别:MEMORY_ONLY / MEMORY_AND_DISK / DISK_ONLY

数据复用越多,缓存收益越明显。内存放不下时选 MEMORY_AND_DISK,避免重算又防止内存溢出。

7Spark SQL 与 Structured Streaming

DataFrame 与 Spark SQL

RDD 很灵活但系统不知道字段名、类型与列结构,难以高级优化。DataFrame = 带 Schema 的分布式表 = 数据 + 结构信息。Spark SQL 让用户用 SQL 描述结构化查询,降低分析门槛。

df = spark.createDataFrame(data, ["name", "age", "major"])
df.createOrReplaceTempView("students")             # 注册临时视图
result = spark.sql("""
  SELECT major, COUNT(*) AS cnt, AVG(age) AS avg_age
  FROM students GROUP BY major ORDER BY cnt DESC""")
result.show()

Catalyst 优化器与 Tungsten

Catalyst 是 Spark SQL 的查询优化器,工作流程:① 逻辑计划创建 → ② 逻辑计划优化(谓词下推 Push down filters、合并操作)→ ③ 物理计划生成 → ④ 代码生成。Tungsten 负责内存管理(堆外内存)与代码生成,让执行层更高效。物理计划中 Exchange 表示发生 Shuffle。

Spark Streaming

日志、点击流、传感器数据持续不断产生,业务希望边产生边分析。

  • 微批(micro-batch)模型:把连续数据流按时间片切成一批批小任务,每批 ≈ RDD,复用 Spark 批处理引擎;
  • 窗口操作:对一个时间窗口内的 micro-batches 做聚合,参数有窗口大小与滑动间隔(如每 10 秒统计最近 1 分钟错误日志);
  • Structured Streaming:用类似批处理的 API 处理无界数据流;
  • Checkpoint:把状态和元数据持久化到可靠存储,用于任务重启恢复状态、故障恢复、窗口统计与有状态计算。
lines = spark.readStream.format("socket").option("host","localhost").option("port",9999).load()
words = lines.select(explode(split(lines.value, " ")).alias("word"))
counts = words.groupBy("word").count()
query = (counts.writeStream.outputMode("complete").format("console")
         .option("checkpointLocation", "chk/wc").start())
query.awaitTermination()
📖 Spark 生态统一引擎之上:Spark SQL(结构化查询)、Spark Streaming / Structured Streaming(流处理)、MLlib(机器学习)、GraphX(图计算)。一套数据、一套 API,资源复用、降低系统复杂度。

重点例题

例题1:如何判断一个算子是宽依赖还是窄依赖?对 Stage 划分有何影响? 判断:看父 RDD 的一个分区是否被子 RDD 的多个分区依赖。窄依赖——子分区只依赖少量父分区(一对一/多对一),如 map、filter、union,可流水执行;宽依赖——父一个分区对应子多个分区,需跨节点重分布数据即 Shuffle,如 reduceByKey、groupByKey、join。
影响:Spark 以宽依赖(Shuffle 边界)划分 Stage。窄依赖在同一 Stage 内流水执行;遇到宽依赖就切出新 Stage。
例题2:为什么 reduceByKey 通常比 groupByKey 更适合大规模聚合? 解:reduceByKey 会在 Map 端先做局部聚合(类似 Combiner),把相同 Key 的值先合并,再 Shuffle 到 Reduce 端做全局聚合,大幅减少网络传输量;而 groupByKey 把每个 Key 的所有原始值都 Shuffle 到 Reduce 端,网络开销大、易内存溢出。因此大规模求和/计数优先用 reduceByKey。
例题3:RDD 某分区丢失后 Spark 如何恢复?为什么不用全量复制? 解:Spark 为每个 RDD 记录 Lineage(血缘)——即它由哪些父 RDD 经哪些转换得到。分区丢失时,Spark 沿血缘链只重算该丢失分区,无需复制所有中间数据。全量复制会占用大量存储与网络;按需重算只在故障时付出代价,更经济。若血缘过长或重算代价高,可用 Checkpoint 截断血缘、持久化中间结果。

🎯自测(点击展开)

Spark 相比 MapReduce 在迭代任务上为何更快?
Spark 把中间结果保留内存、可复用缓存数据,避免 MapReduce 每轮反复读写磁盘。
Spark 按什么划分 Stage?
按 Shuffle 边界(宽依赖)划分 Stage,Stage 内部窄依赖流水执行。
窄依赖和宽依赖的区别?各举一例。
窄依赖子分区只依赖少量父分区(map、filter),无 Shuffle;宽依赖父一个分区对应子多个分区(reduceByKey、join),产生 Shuffle。
RDD 的五大特性是什么?
分区、依赖、计算函数、分区器、首选位置。
Transformation 和 Action 的区别?惰性执行指什么?
Transformation 返回新 RDD、惰性不立即执行;Action 触发真正计算。惰性执行指 Transformation 只构建逻辑链,等 Action 才统一执行。
RDD 分区丢失后靠什么恢复?
靠 Lineage 血缘,沿血缘链重算丢失的分区,而非全量复制。

📝强化题库

选择题点选即时判分;填空题输入后"检查"或"显示答案"。

已答 0/0答对 0正确率
已答 0/0答对 0