数据倾斜的三大高频场景

说实话,我这些年排查过的数据倾斜问题,十有八九都逃不出这三个场景:Join、GroupBy、Distinct/CountDistinct。你想想看,这三个操作恰恰是Spark里最吃Shuffle的操作,也是数据分布最容易出幺蛾子的地方。

我记得有一次帮一个电商团队排查性能问题,他们的ETL任务跑了一个多小时还没跑完。我一看DAG图,Stage 7卡了大半时间,点进去一看——典型的Join倾斜。嗯,这种问题见得多了,今天就把这三个场景掰开揉碎了讲清楚。

核心认知:数据倾斜的本质不是数据量大,而是数据分布不均匀。一个分区处理了90%的数据,其他分区在闲着,这才是真正的瓶颈。
数据倾斜三大高频场景 Shuffle 数据倾斜 Join 操作倾斜 热点Key导致广播失效 GroupBy 操作倾斜 聚合Key分布极不均匀 Distinct 倾斜 全量去重导致单分区爆炸 大表 Join 小表:小表热点Key 大表 Join 大表:空Key/默认值 城市/品类等天然倾斜维度 时间窗口内数据集中爆发 CountDistinct 全量去重 Bitmap/布隆过滤器误判 诊断方法:Spark UI → Stage详情 → 分区数据量对比

Join 操作中的倾斜

Join倾斜是我在实际项目中遇到最多的类型。说白了,就是某个Key的数据量特别大,导致这个Key对应的分区要处理海量数据,其他分区却闲着。

我举个例子你就明白了。假设你在做用户订单分析,要Join用户表和订单表。正常情况下,每个用户的数据量差不多。但突然有一天,有个叫"测试用户"的Key,关联了上千万条订单数据——嗯,这就是典型的数据倾斜Key。

我的经验:遇到Join倾斜,先别急着调参数。先看看是不是有"空Key"或"默认值"在作怪。我曾经排查过一个案例,发现"null"这个Key占了40%的数据量,加上一个filter就解决了。

Join倾斜的典型表现:

  • 大表 Join 小表:小表广播后,大表的某个分区数据量远超其他分区
  • 大表 Join 大表:两个大表的Join Key分布都不均匀,Shuffle后某个分区数据爆炸
  • 空Key/脏数据:null或默认值(如"0"、"-1")作为Join Key,导致大量数据涌入同一分区

怎么快速定位?我个人习惯在Spark UI里看Stage的Shuffle Read Size。如果某个Task的读取量是其他Task的10倍以上,基本可以断定是Join倾斜了。

GroupBy 操作中的倾斜

GroupBy倾斜,说白了就是分组聚合时,某个组的数据量特别大。你想想看,按城市分组,北京上海的数据量肯定比三线城市大得多。这种天然倾斜是业务逻辑决定的,没法避免。

我记得有一次帮一个广告平台做优化,他们的报表任务每天跑3个小时。我一看GroupBy的Key是"广告位ID",有个头部广告位的数据量是其他广告位的100倍。这就是典型的热点Key问题。

倾斜类型 典型场景 倾斜程度
天然倾斜 城市、品类、渠道等业务维度 中等(10-50倍)
热点倾斜 头部用户、爆款商品、热门广告位 严重(100倍以上)
时间倾斜 大促期间、整点流量高峰 突发性(短期暴增)
避坑指南:我曾经犯过一个错误——看到GroupBy倾斜就盲目加并行度。结果呢?分区多了,但热点Key还是在一个分区里,根本没用。GroupBy倾斜的核心是Key分布问题,不是分区数量问题。

GroupBy倾斜的排查方法:

  1. 在Spark UI里看Shuffle Write阶段,哪个Task写的数据量最大
  2. 用df.groupBy("key").count().orderBy(desc("count")).show(10)看Top Key
  3. 如果Top 1 Key的数据量占总量30%以上,基本就是倾斜了

Distinct / CountDistinct 中的倾斜

这个场景比较隐蔽,很多人容易忽略。Distinct操作本质上也是GroupBy,但它去重的是全量数据,所以倾斜起来更狠。

你想想看,CountDistinct需要把所有数据Shuffle到同一个分区去重。如果某个Key对应的数据量特别大,这个分区就直接炸了。我见过一个案例,CountDistinct一个高基数字段,结果某个分区的数据量是其他分区的500倍——Task直接OOM了。

关键区别:GroupBy倾斜是"某个组数据多",CountDistinct倾斜是"某个分区要去重所有数据"。后者对内存的冲击更大,更容易导致OOM。

CountDistinct的典型问题:

  • 全量去重:df.select(countDistinct("col"))会把所有数据拉到同一个分区
  • 分组去重:df.groupBy("key").agg(countDistinct("col")),如果某个Key的数据量特别大,这个分区就扛不住了
  • Bitmap误判:用Bitmap做近似去重时,如果数据分布不均匀,Bitmap的压缩效果会变差

我个人建议,如果业务允许,优先用近似去重(如HyperLogLog)替代精确去重。误差在1%以内,性能提升10倍以上。当然,如果业务要求精确去重,那就得用后面章节讲的两阶段聚合或Salting方案了。

一个小技巧:如果你必须用CountDistinct,可以先对数据做预聚合。比如先按"key+随机前缀"做一次去重,再对结果做第二次去重。这样能把大分区的压力分散到多个分区。