shuffle、AQE

一、背景和过程
Shuffle是把分散在不同节点的「相同 Key 数据」聚到一起,核心规则是「相同 Key 去同一个地方」,这个过程中就会涉及到几个问题,

  1. 本地分类(Map 端) 每个教室先把手里的试卷,按「班级」分成几摞(比如一班一摞、二班一摞),写在小纸条上标记(避免搞混) 先在本地整理,减少后续分发的混乱
  2. 临时存放(落盘) 把分好的试卷暂时放在教室的桌子上(而不是抱在怀里),避免抱太多掉地上(对应内存溢出) 防止内存装不下,先存磁盘
  3. 分发试卷(网络传输) 每个教室把非自己负责的试卷,送到对应教室:比如教室 1 把二班的试卷送到教室 3,把三班的送到教室 2 让「同一个班级」的试卷聚到同一个教室
  4. 汇总计算(Reduce 端) 目标教室收到所有本班试卷,汇总所有分数,计算平均分 聚好数据后,才能做分组 / 关联计算
    二、实战核心,避坑优选
    数据裁剪是前提:Shuffle 前必须通过WHERE(行)、列裁剪、分区裁剪减少数据量,否则调优效果为 0;
    优先用 AQE 兜底:全开 AQE 配置,让 Spark 自动处理倾斜、小分区、Join 策略选择;
    控制并行度:默认 200 太小,大表场景设 1000~2000,避免单分区数据过载;
    警惕热点 Key/NULL 值:这两类场景 AQE 仅能临时缓解,需手动打散;
    避免重复 Shuffle:合并多次 Shuffle 逻辑(如先聚合后 Join)。
    三、具体措施
    AQE配置
#
spark = SparkSession.builder 
.appName("Shuffle_AQE_Opt") 
# AQE总开关(核心)
.config("spark.sql.adaptive.enabled", "true")
# 自动合并小分区(减少调度开销)
.config("spark.sql.adaptive.coalescePartitions.enabled", "true")
# 自动拆分倾斜分区(解决数据倾斜)
.config("spark.sql.adaptive.skewJoin.enabled", "true")
# 倾斜判定阈值(2GB,适配大表)
.config("spark.sql.adaptive.skewJoin.skewThreshold", "2147483648")
# Shuffle并行度(AQE会基于此动态调整)
.config("spark.sql.shuffle.partitions", "1000")
# 堆外内存(避免Shuffle OOM)
.config("spark.memory.offHeap.enabled", "true")
.config("spark.memory.offHeap.size", "4g")
.getOrCreate()`

AQE 能自动解决的 Shuffle 问题:
1.小分区过多:自动合并成合理大小的分区;比如:Shuffle 后:比如你设置spark.sql.shuffle.partitions=200,但实际数据量很小(比如只有 1 万条),会导致 200 个分区中,大部分分
区只有几十条甚至 1 条数据;
2.随机倾斜(非静态热点):拆分超大分区
3.选错 Join 策略:小表自动用 Broadcast Join,大表用 Sort Merge Join;
4.Shuffle 并行度不合理:动态调整分区数,平衡并行度和调度开销。
四、理解
1.AEQ解决的是随机倾斜,但对静态热点数据无效
答:倾斜的 key 是固定不变的(比如 key=「北京」),倾斜的 key 是随机出现的(这次是 A,下次是 B),比如「广州」平时订单量正常某天突然倾斜就属于随机倾斜,如果所有的匿名用户
都使用0来代替,那可能会有很多数据在计算的时候会发生倾斜,这就是热点数据。
2.遇到静态热点数据时应该怎么办?
答:方案一把热点 key(比如「北京」)的数据单独拿出来计算,再和其他数据的结果合并,注意两个SQL是并行执行
` 步骤1:单独计算热点key(北京)的聚合结果
select city, sum(amount) as total_amount
from order_table
where city = '北京'
group by city;

步骤2:计算非热点key的聚合结果
select city, sum(amount) as total_amount
from order_table
where city != '北京'
group by city;

-- 步骤3:合并结果
select * from (
select city, total_amount from 热点结果
union all
select city, total_amount from 非热点结果
);`
方案二:加盐(Salting)拆分热点 key
-- 对热点key「北京」加盐,拆分成10个分区
select
case when city = '北京' then concat(city, '_', rand()*10) else city end as city_salt,
amount
from order_table
group by city_salt;

-- 后续再把加盐的key合并回来
select
split(city_salt, '')[0] as city,
sum(amount) as total_amount
from 加盐后的表
group by split(city_salt, '
')[0];
方案二、对于随机倾斜,开启spark.sql.adaptive.skewJoin.enabled=true和调大 Shuffle 分区数(把spark.sql.shuffle.partitions从默认 200 调大(比如 500/1000),降低哈希冲突的概率,减少随机倾斜的出现频率:),
Spark 会:
运行时实时统计每个分区的大小;
识别出随机出现的倾斜分区;
自动拆分成多个小分区并行处理

posted @ 2026-01-09 11:59  秋水依然  阅读(3)  评论(0)    收藏  举报