Spark Core 面试专题
概述
Spark Core 是面试高频区:RDD 特性、算子对比、重分区、缓存与 Checkpoint、血统容错、内存与 Shuffle 调优。本文按原理题、对比题、场景设计题三类整理高频问答。
一、原理类问答
Q1:RDD 是什么?五大特性?
| 特性 | 说明 |
|---|---|
| 分区列表 | 数据分布的分区集合 |
| 分区计算函数 | 每个分区的计算逻辑 |
| 依赖关系 | 窄/宽依赖 |
| 分区器 | 键值数据的 hash/range 分区 |
| 首选位置 | 数据本地性提示 |
RDD 只记录计算链条(血统),不实际存数据。
Q2:RDD 为什么惰性?
转换算子只记录依赖,Action 才触发计算。好处:构建完整 DAG 后统一优化、合并算子、减少中间落盘。
Q3:窄依赖与宽依赖的区别?
| 依赖 | 特点 | 算子 |
|---|---|---|
| 窄 | 父分区只对应一个子分区,管道式 | map/filter/union |
| 宽 | 父分区对应多个子分区,需 Shuffle | groupByKey/reduceByKey/join |
宽依赖产生 Shuffle,也是 Stage 划分的依据。
Q4:Spark 如何保证容错?
| 机制 | 说明 |
|---|---|
| 血统 Lineage | 分区丢失沿依赖链重算 |
| 缓存 | 中间结果常驻内存防重算 |
| Checkpoint | 写 HDFS 断链 |
| Task 重试 | 失败任务按次数重试 |
Q5:Spark 为什么比 MapReduce 快?
| 因素 | 说明 |
|---|---|
| 内存计算 | 中间结果驻留内存 |
| DAG 引擎 | 多 Stage 管道式执行,减少落盘 |
| 迭代友好 | 数据常驻,机器学习等迭代快 |
| 优化器 | Catalyst/Tungsten 优化执行 |
二、算子对比类问答
Q6:reduceByKey 与 groupByKey 的区别?
groupByKey:先全量 Shuffle 到 Reduce 端再聚合
reduceByKey:Map 端预聚合(combine),Shuffle 数据量更小结论:能预聚合的场景用 reduceByKey,Shuffle 量可减少数倍。
Q7:map 与 flatMap 的区别?
| 算子 | 输出 |
|---|---|
| map | 1 输入 → 1 输出 |
| flatMap | 1 输入 → 0~N 输出(扁平化) |
scala
rdd.map(_.split(" ")) // Array[Array[String]]
rdd.flatMap(_.split(" ")) // Array[String]Q8:coalesce 与 repartition 的区别?
| 算子 | Shuffle | 场景 |
|---|---|---|
| coalesce | 默认无 Shuffle | 缩小分区 |
| repartition | 必有 Shuffle | 增大或减小分区 |
coalesce(2) 缩小分区无 Shuffle;repartition(100) 增大分区需 Shuffle。
Q9:cache 与 persist 的区别?
| 对比 | cache | persist |
|---|---|---|
| 存储级别 | MEMORY_ONLY | 可指定任意级别 |
| 本质 | persist(MEMORY_ONLY) 简写 | 完整 API |
Q10:cache 与 checkpoint 的区别?
| 对比 | cache | checkpoint |
|---|---|---|
| 存储位置 | 内存/本地磁盘 | HDFS |
| 血统 | 保留 | 切断 |
| 生命周期 | Executor 退出失效 | 持久 |
| 场景 | 多次复用 | 长链/迭代 |
三、场景设计类问答
Q11:数据倾斜怎么处理?
症状:个别 Task 极慢或 OOM
方案:
1. 加盐拆 key:热点 key 加随机前缀,聚合后去盐再聚合
2. 两阶段聚合:本地预聚合 + 全局聚合
3. 提高并行度:增大分区数
4. 过滤极端 key:单独处理后 union
5. 广播 join:小表广播,避免大 ShuffleQ12:Execuctor OOM 如何排查?
步骤:
1. 看日志确定是执行内存还是存储内存
2. 执行内存 OOM:
- 数据倾斜(单 key 过大)
- 并行度不足(分区数少)
- Shuffle 聚合缓冲过大
3. 存储内存 OOM:
- 缓存数据过大
- storageFraction 太小
4. Driver OOM:
- collect 大结果集
- 广播变量过大Q13:如何提升 Shuffle 性能?
| 手段 | 说明 |
|---|---|
| reduceByKey 替代 groupByKey | map 端预聚合 |
| 开启压缩 | 网络/磁盘 IO 减少 |
| Kryo 序列化 | 更快更小 |
| 合理分区数 | 并行度与内存平衡 |
| 治倾斜 | 加盐/两阶段聚合 |
Q14:小文件过多怎么处理?
场景:大量小文件写入 HDFS 或 Hive
方案:
1. 控制输出分区数(repartition/coalesce)
2. Shuffle 分区数 = 输出文件数
3. 用 DataFrame 的 coalesce 写
4. 合并小文件(如 Hive 的合并小文件参数)Q15:Spark 作业运行很慢,如何定位?
定位顺序:
1. 看 Stage 耗时分布(UI)
2. 是否有明显慢 Task(倾斜/慢节点)
3. 是否 Shuffle 量巨大(预聚合/压缩)
4. 是否 GC 频繁(内存配置)
5. 本地性是否差(ANY 多)四、高频易错点
| 易错点 | 正确理解 |
|---|---|
| 缓存是惰性的 | 需 Action 后才真正缓存 |
| 分区数 = Task 数 | 并行度由分区决定 |
| cache 不切断血统 | 只有 checkpoint 切断 |
| 惰性转换不执行 | 转换只建依赖 |
| collect 会打满 Driver | 大数据量不可 collect |
| 闭包序列化问题 | 算子内对象需可序列化 |
五、速答清单
| 高频题 | 一句话答案 |
|---|---|
| Spark 的宽窄依赖 | 父分区对应 1 个子分区为窄,多个为宽 |
| Stage 如何划分 | 按宽依赖切分 |
| 为什么快 | 内存计算 + DAG + 迭代友好 |
| 怎么治倾斜 | 加盐/两阶段/广播/提高并行度 |
| cache 和 checkpoint | cache 留血统,checkpoint 断链 |
| OOM 排查 | 先分执行/存储/Driver,再对因处理 |