数据倾斜的三大高频场景
说实话,我这些年排查过的数据倾斜问题,十有八九都逃不出这三个场景:Join、GroupBy、Distinct/CountDistinct。你想想看,这三个操作恰恰是Spark里最吃Shuffle的操作,也是数据分布最容易出幺蛾子的地方。
我记得有一次帮一个电商团队排查性能问题,他们的ETL任务跑了一个多小时还没跑完。我一看DAG图,Stage 7卡了大半时间,点进去一看——典型的Join倾斜。嗯,这种问题见得多了,今天就把这三个场景掰开揉碎了讲清楚。
Join 操作中的倾斜
Join倾斜是我在实际项目中遇到最多的类型。说白了,就是某个Key的数据量特别大,导致这个Key对应的分区要处理海量数据,其他分区却闲着。
我举个例子你就明白了。假设你在做用户订单分析,要Join用户表和订单表。正常情况下,每个用户的数据量差不多。但突然有一天,有个叫"测试用户"的Key,关联了上千万条订单数据——嗯,这就是典型的数据倾斜Key。
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倾斜的排查方法:
- 在Spark UI里看Shuffle Write阶段,哪个Task写的数据量最大
- 用
df.groupBy("key").count().orderBy(desc("count")).show(10)看Top Key - 如果Top 1 Key的数据量占总量30%以上,基本就是倾斜了
Distinct / CountDistinct 中的倾斜
这个场景比较隐蔽,很多人容易忽略。Distinct操作本质上也是GroupBy,但它去重的是全量数据,所以倾斜起来更狠。
你想想看,CountDistinct需要把所有数据Shuffle到同一个分区去重。如果某个Key对应的数据量特别大,这个分区就直接炸了。我见过一个案例,CountDistinct一个高基数字段,结果某个分区的数据量是其他分区的500倍——Task直接OOM了。
CountDistinct的典型问题:
- 全量去重:
df.select(countDistinct("col"))会把所有数据拉到同一个分区 - 分组去重:
df.groupBy("key").agg(countDistinct("col")),如果某个Key的数据量特别大,这个分区就扛不住了 - Bitmap误判:用Bitmap做近似去重时,如果数据分布不均匀,Bitmap的压缩效果会变差
我个人建议,如果业务允许,优先用近似去重(如HyperLogLog)替代精确去重。误差在1%以内,性能提升10倍以上。当然,如果业务要求精确去重,那就得用后面章节讲的两阶段聚合或Salting方案了。