在 Spark 作業優化中,合理控制 RDD 分區數直接影響並行度、Shuffle 開銷與輸出文件數。coalesce 與 repartition 是兩大核心 API,本文從源碼層面剖析其實現差異,並提供場景化選型建議。
| 特性 | coalesce | repartition |
|---|---|---|
| 底層實現 | CoalescedRDD(窄依賴)或 ShuffledRDD(寬依賴) | coalesce(numPartitions, shuffle=true) 必發生 Shuffle |
| 預設 Shuffle | false | true(強制) |
| 減少分區 | ✅ 高效(無 Shuffle) | ⚠️ 觸發全量 Shuffle |
| 增加分區 | ❌ 無效(shuffle=false 時) | ✅ 必須 Shuffle 重分佈 |
| 適用場景 | Filter 後分區稀疏、輸出文件合併 | Join 前預分區、數據傾斜緩解 |
def coalesce(numPartitions: Int, shuffle: Boolean = false): RDD[T] = {
if (shuffle) {
// 經過 HashPartitioner 重新分佈,產生 ShuffledRDD → CoalescedRDD
new CoalescedRDD(
new ShuffledRDD[Int, T, T](
mapPartitionsWithIndex(distributePartition),
new HashPartitioner(numPartitions)
),
numPartitions
).values
} else {
// 窄依賴:直接合併分區,父分區 1:N 映射到子分區
new CoalescedRDD(this, numPartitions)
}
}
關鍵點:
shuffle=false 時,採用 窄依賴,父分區只被少數子分區引用,無網絡傳輸shuffle=truedef repartition(numPartitions: Int): RDD[T] =
coalesce(numPartitions, shuffle = true)
本質是 強制全量 Shuffle,經 HashPartitioner 按鍵值重新分佈,保證數據均勻但代價高昂。
窄依賴 (Narrow) 寬依賴 (Wide) 父分區 → 1 子分區 父分區 → 多子分區 Map, Filter, coalesce Join, GroupBy, repartition Pipeline 執行 Stage 邊界,需 Shuffle Write/Read
coalesce(shuffle=false) 保持窄依賴,Task 可鏈式執行;repartition 強制切分 Stage,產生 Shuffle 文件。
// ✅ 推薦:無 Shuffle 合併,保持窄依賴
val optimized = filteredRDD.coalesce(10, shuffle = false)
// ❌ 避免:觸發不必要的全量 Shuffle
val bad = filteredRDD.repartition(10)
// ✅ 必須開啟 Shuffle
val expanded = rdd.coalesce(1000, shuffle = true)
// 或等價寫法
val expanded = rdd.repartition(1000)
Exception: Unable to acquire 16777216 bytes of memory
at UnsafeExternalSorter.acquireNewPage...
解決: 強制 shuffle=true,將數據重新分佈到多個 Executor,避免單 Task 內存壓力:
val safe = hugeRDD.coalesce(5, shuffle = true)
// 寫入 HDFS/S3 前合併小文件
df.coalesce(100).write.parquet("/output") // 生成 100 個文件
coalesce(shuffle=false) 減少分區,零網絡開銷repartition 或 coalesce(shuffle=true) 打散熱點Shuffle Read/Write、Task 時間分佈、GC 時間spark.dynamicAllocation.enabled=true 配合 coalesce 自動縮容核心原則:能不 Shuffle 就不 Shuffle。
coalesce是精準手術刀,repartition是重組核彈——按業務場景精準選擇。