4. 数据倾斜量化指标

聊了这么多倾斜的现象和原理,你可能会问:到底怎么才算倾斜? 光靠肉眼看 Spark UI 上的长尾任务,太主观了。我个人的习惯是——用三个量化指标,把倾斜程度变成数字。这样你调优前后一对比,效果一目了然。

今天我们就来拆解这三个核心指标:任务执行时间标准差、数据分区大小变异系数、Shuffle 数据量峰值/均值比。嗯,名字有点长,但用起来其实很简单。

4.1 任务执行时间标准差

先问个问题:为什么看平均值不够?

我记得有一次帮一个电商团队排查问题,他们看的是「任务平均执行时间」,只有 30 秒,觉得还行。但我一算标准差——45 秒。这说明什么?说明有的任务 5 秒跑完,有的跑了两分钟。平均值把极端情况全掩盖了。

标准差(Standard Deviation) 衡量的是任务执行时间的离散程度。公式不复杂:

σ = sqrt( Σ(xi - μ)² / N )
  • xi:每个任务的执行时间
  • μ:所有任务的平均执行时间
  • N:任务总数

经验阈值:

  • σ / μ < 0.3:任务分布均匀,基本无倾斜
  • 0.3 ≤ σ / μ < 0.8:存在轻度倾斜,需要关注
  • σ / μ ≥ 0.8:严重倾斜,必须干预

我在项目中一般用 变异系数(CV = σ / μ) 来归一化,这样不同规模的任务之间也能横向对比。你想想看,一个 100 个 task 的 Stage,和 1000 个 task 的 Stage,直接用标准差比不公平,但用 CV 就公平了。

实操建议: 在 Spark UI 的 Stages 页面,把每个任务的执行时间导出到 Excel,用 STDEV.P 函数一算就出来。或者写个简单的脚本解析 event log。

4.2 数据分区大小变异系数

这个指标更直接——看每个分区里到底装了多少数据。说白了,就是检查数据在分区之间的分配是否均匀。

我曾经遇到过一个极端案例:某个表按 user_id 分区,结果一个超级用户占了全表 40% 的数据。那个分区的大小是其他分区的 200 倍。你想想看,跑 Join 的时候,那个大分区就是整个作业的瓶颈。

计算方式跟上面类似:

CV_size = σ_size / μ_size
  • σ_size:各分区大小的标准差
  • μ_size:各分区大小的平均值

怎么拿到分区大小?我常用的方法:

  1. Spark UI → Storage 页面:直接看 RDD 或 DataFrame 各分区的大小
  2. 代码里用 glom() 算子:把每个分区的数据收集成数组,然后算大小
  3. Shuffle 中间文件:看 Shuffle Write 阶段各分区的输出大小

注意: 用 glom() 的时候要小心,如果分区数据量太大,容易 OOM。我一般只在开发环境或者采样数据上用。

这里有个简单的采样脚本,你可以跑一下看看:

// 假设 df 是你的 DataFrame
val partitionSizes = df.rdd.glom().map(_.length).collect()
val mean = partitionSizes.sum.toDouble / partitionSizes.length
val std = math.sqrt(partitionSizes.map(x => math.pow(x - mean, 2)).sum / partitionSizes.length)
val cv = std / mean
println(s"分区大小变异系数: $cv")

如果 CV > 1.0,说明分区大小的离散程度已经超过平均值了——这基本就是重度倾斜。

4.3 Shuffle 数据量峰值/均值比

第三个指标,我把它叫做 「倾斜放大镜」。它专门用来衡量 Shuffle 阶段的数据倾斜。

公式很简单:

Ratio = Max(Shuffle_Write_Size) / Avg(Shuffle_Write_Size)

什么意思呢?就是看 Shuffle 写数据的时候,数据量最大的那个分区,是平均分区的多少倍。

我在项目中见过最夸张的一次,Ratio 达到了 150:1。也就是说,一个分区写了 150 份数据,其他分区平均只写 1 份。那个 Stage 跑了 40 分钟,而其他类似的 Stage 只要 2 分钟。

Ratio 范围 倾斜程度 建议操作
1.0 - 2.0 正常 无需处理
2.0 - 5.0 轻度倾斜 观察,可考虑加盐或调整分区键
5.0 - 10.0 中度倾斜 建议干预,使用两阶段聚合或倾斜 Join 优化
> 10.0 重度倾斜 必须处理,否则作业可能失败

这个指标的好处是——你不需要写代码。直接在 Spark UI 的 Stages 页面,点进某个 Stage,看「Shuffle Write Size」那一列,最大值和平均值一目了然。

我的诊断流程:

  1. 先看 任务执行时间 CV,快速判断有没有倾斜
  2. 如果有,再看 分区大小 CV,定位是哪个 Stage 倾斜
  3. 最后看 Shuffle 峰值/均值比,确认倾斜的严重程度

三步下来,倾斜的全貌就清楚了。

4.4 三个指标的关系与综合判断

这三个指标不是孤立的,它们从不同角度描述同一个问题:

  • 任务执行时间标准差:看的是「结果」——任务跑得快慢差异大不大
  • 分区大小变异系数:看的是「原因」——数据分布本身就不均匀
  • Shuffle 峰值/均值比:看的是「过程」——Shuffle 阶段的数据倾斜有多严重

我一般会画一个这样的矩阵来判断优先级:

数据倾斜量化指标诊断矩阵 任务执行时间 CV 看结果:任务快慢差异 分区大小 CV 看原因:数据分布均匀度 Shuffle 峰值/均值比 看过程:Shuffle 倾斜程度 三个指标同时偏高 → 确认存在数据倾斜 仅一个指标偏高 → 可能是数据波动或资源竞争,需进一步排查 输出:倾斜等级 + 倾斜位置 + 建议调优策略 轻度 → 观察 | 中度 → 加盐/调整分区键 | 重度 → 两阶段聚合/倾斜 Join 💡 建议:每次调优前后都记录这三个指标,用数据说话,而不是凭感觉

你看,这三个指标就像三盏灯——一盏亮了可能是小问题,三盏都亮了,那肯定是倾斜没跑了。

我的习惯: 每次调优前后,我都会把这几个指标记下来,做成一个简单的表格。比如:

  • 调优前:CV=1.2, Ratio=25:1 → 重度倾斜
  • 加盐后:CV=0.4, Ratio=3:1 → 轻度倾斜
  • 优化后:CV=0.2, Ratio=1.5:1 → 正常

这样汇报的时候,领导一看就明白你做了什么,效果怎么样。