🎓 总站 🏠 本课目录 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 推动了离线大数据处理,但其执行模型偏"批处理+磁盘中转"。对日志清洗这类单次扫描够用,但对机器学习、交互式查询、图计算等迭代优化任务,中间结果反复落盘非常慢。

💡 通俗理解:MapReduce 是什么?MapReduce 就像一个"分拣工厂"处理快递:Map 阶段(分拣)——把一堆快递按地区分开,每个工人负责一个地区,各自打包好;Shuffle 阶段(转运)——把同一地区的包裹集中到一起;Reduce 阶段(汇总)——每个地区的负责人做最终处理(如清点数量)。这个模型很简单,但问题是:每一步处理完都要把结果写到磁盘上(落盘),再从磁盘读出来做下一步。如果要反复处理(比如机器学习训练 100 轮),每轮都读写磁盘,非常慢。
💡 Spark 核心思想① 尽量把中间结果保留在内存,减少磁盘 I/O;② DAG 执行引擎;③ 统一批处理与流处理。
💡 通俗理解:Spark 为什么比 MapReduce 快?继续用快递工厂的比喻:MapReduce 每做完一步就把包裹搬回仓库(写磁盘),下一步再从仓库搬出来(读磁盘)。Spark 则聪明得多——它让工人把包裹放在手边的工作台上(内存),做完一步直接在工作台上继续下一步,省去了反复搬运的时间。而且 Spark 有"DAG 优化器",能提前规划好最优的处理顺序,减少不必要的中间步骤。所以 Spark 在需要反复处理数据的场景(如机器学习)比 MapReduce 快 10~100 倍。
维度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 间的转换形成依赖关系,可管道化、避免中间数据存储。

💡 通俗理解:RDD 是什么?RDD 就像一个"不可变的分布式 Excel 表格"。它把大数据分散存储在多台机器上(分布式),你不能直接修改它,每次操作都会生成一个新的 RDD(弹性/不可变)。比如你有一个包含全校学生成绩的 RDD,你用 filter 筛选出"90 分以上的",得到一个新的 RDD;再用 map 算出平均分,又得到一个新的 RDD。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