以下是针对 Spark 性能调优黄金法则的深度解析与补充,结合 Catalyst 优化器和 Tungsten 执行引擎的核心优化技术,形成完整的调优体系:
1. 避免 Shuffle(最高优先级)
- Broadcast Join:
- 条件: 小表需能放入 Executor 内存(默认 < 10MB,可通过
spark.sql.autoBroadcastJoinThreshold
调整至 100MB)。 - 手动指定:
df1.join(broadcast(df2), "key")
。
- 条件: 小表需能放入 Executor 内存(默认 < 10MB,可通过
reduceByKey
vsgroupByKey
:groupByKey
: 全量数据 Shuffle → OOM 风险高。reduceByKey
: 本地 Combine 后再 Shuffle → 网络传输量减少 90%+。
partitionBy
预分区:- 对需多次 Join/聚合的 RDD 提前按 Key 分区,避免后续操作触发 Shuffle。
2. 优化无法避免的 Shuffle
- 调整分区数:
- 参数:
spark.sql.shuffle.partitions
(默认 200)→ SQL/DataFramespark.default.parallelism
(默认 CPU 核心数)→ RDD
- 公式参考:
分区数 = min(集群总核心数 * 4, 数据量 / 128MB)
。
- 参数:
- Kryo 序列化:
spark.conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") spark.conf.set("spark.kryo.registrationRequired", "true") spark.conf.registerKryoClasses(Array(classOf[MyCustomClass]))
- 效果: 序列化速度 2-5x 提升,体积 减少 50-80%。
3. 内存优化与 GC 调优
- 内存结构:
- 关键配置:
参数 默认值 优化建议 spark.memory.fraction
0.6 缓存需求大 → 0.7 spark.memory.storageFraction
0.5 计算内存不足 → 0.3 spark.memory.offHeap.enabled
false 堆外内存 > 32GB 时启用 spark.memory.offHeap.size
0 建议不超过物理内存 70% - 持久化级别选择:
级别 适用场景 MEMORY_ONLY
内存充足的小数据集 MEMORY_ONLY_SER
内存受限 + 可接受 CPU 开销 MEMORY_AND_DISK_SER
超大数据集 + 允许磁盘溢出
4. 并行度与数据分区
- 分区大小黄金法则: 128MB - 256MB/分区
- 计算公式:
目标分区数 = 数据总大小 / 128MB
- 场景调整:
// 读取时调整 spark.read.option("maxPartitionBytes", "134217728") // 128MB // 处理中调整 df.repartition(1000) // 触发 Shuffle df.coalesce(100) // 无 Shuffle 合并
5. 数据倾斜解决方案
方法 1:随机盐(通用方案)
// 大表加盐
val saltedDF = bigDF.withColumn("salted_key",
concat($"key", lit("_"), floor(rand() * 100)))
// 小表扩容
val saltedSmallDF = smallDF
.withColumn("salt", explode(lit((0 until 100).toArray)))
.withColumn("salted_key", concat($"key", lit("_"), $"salt"))
// 盐化后 Join
saltedDF.join(saltedSmallDF, "salted_key")
方法 2:分离倾斜 Key(精准打击)
// 1. 识别倾斜 Key(TOP 1)
val skewKeys = df.groupBy("key").count()
.orderBy(desc("count")).limit(1).select("key").collect()
// 2. 拆解数据集
val skewedDF = df.filter($"key" === skewKeys(0)) // 倾斜部分
val normalDF = df.filter($"key" =!= skewKeys(0)) // 正常部分
// 3. 分别处理 + 合并
val resultSkew = skewedDF.join(broadcast(smallDF), "key")
val resultNormal = normalDF.join(broadcast(smallDF), "key")
resultSkew.union(resultNormal)
6. 资源分配策略(YARN 示例)
参数 | 计算公式 | 示例(100 核集群) |
---|---|---|
Executor 数量 | 总核数 / 单 Executor 核数 | 100 / 5 = 20 |
Executor 核数 | 3-5(避免过多导致 HDFS 连接竞争) | --executor-cores 5 |
Executor 内存 | (容器内存 - 1GB) * 0.9 | --executor-memory 24G |
Driver 内存 | 广播大表时需增加 | --driver-memory 8G |
⚠️ 避坑指南: Executor 堆内存 > 32GB 时,启用 G1GC:
spark-submit --conf "spark.executor.extraJavaOptions=-XX:+UseG1GC"
7. Catalyst & Tungsten 深度优化
Catalyst 优化器(逻辑优化)
- 优化触发条件: 使用 DataFrame/Dataset/SQL API(非 RDD!)
- 核心优化技术:
技术 效果 示例 谓词下推 提前过滤数据 df.filter("age>30").join(...)
→ 先过滤再 Join列剪裁 跳过无关列读取 SELECT name FROM tb
→ Parquet 只读 name 列常量折叠 预先计算常量 SELECT 1+1 AS two
→ 直接输出2
成本优化 自动选择 Join 策略 小表 → BroadcastJoin;大表 → SortMergeJoin
Tungsten 执行引擎(物理优化)
- 三大革新:
- 二进制内存格式:
- 数据以 字节数组 存储,避免 JVM 对象开销
- 减少 GC 压力 + 内存占用降低 40%
- 全阶段代码生成:
- 将查询编译为 JVM 字节码(非逐行解释执行)
- 性能提升 10-100x(尤其聚合、过滤操作)
- Cache Locality:
- 优化 CPU 缓存命中率,利用向量化指令
- 二进制内存格式:
调优实践路线图
黄金法则: 始终从 Web UI 指标出发,结合业务逻辑选择针对性优化策略。Catalyst 和 Tungsten 是 Spark 的“自动驾驶系统”,但开发者需通过优化代码和配置为其铺平道路。