94% 的集群在空等——数据倾斜实测

94% 的集群在空等------数据倾斜实测

200 万条记录 shuffle 到 64 个 reducer。同样的代码,只换数据分布:

markdown 复制代码
数据分布                      最少      平均       最多    最大/平均   最大占比
--------------------------------------------------------------------------
均匀分布的 key              30,006    31,250    32,735     1.05x     1.6%
Zipf 分布的 key (真实数据)    4,458    31,250   531,944    17.02x    26.6%

一个 reducer 吃掉了 26.6% 的数据,是平均值的 17 倍。

而分布式作业的完成时间由最慢的那个任务决定(木桶效应)。所以这个作业的实际耗时是平均负载的 17 倍------

换句话说,94% 的集群算力在空等。

你开 64 台机器,实际上只有 3.8 台在干活。

为什么真实数据一定是倾斜的

Zipf 分布不是我刻意构造的极端情况,它是现实世界的默认形态:

ini 复制代码
Zipf 数据的 key 频次: top1 = 519,894 条 (26.0%), top10 合计 59.4%
总共 54,015 个不同的 key

想想你的业务:

  • 按用户 ID 聚合 → 有几个超级用户/爬虫/测试账号的数据量是普通用户的万倍
  • 按商品 ID 聚合 → 爆款商品的订单量碾压长尾
  • 按城市聚合 → 北上广深 vs 县城
  • 按错误码 聚合 → 99% 是 200
  • 按日期聚合 → 大促那天

还有最阴的一种:NULL 和空字符串。所有缺失值哈希到同一个 key,而缺失值往往占比极高。

均匀分布是需要刻意制造的,倾斜才是自然状态。

加机器完全无效

这是我觉得最该贴在墙上的一组数据:

markdown 复制代码
reducer 数     平均负载      最大负载     最大/平均      理论耗时
------------------------------------------------------------
    16        125,000     567,508      4.54x      567,508 条
    64         31,250     536,617     17.17x      536,617 条
   256          7,812     524,952     67.19x      524,952 条
  1024          1,953     520,084    266.28x      520,084 条
  4096            488     519,994   1064.95x      519,994 条

reducer 从 16 加到 4096(256 倍),最大负载从 567,508 降到 519,994------降了 8%。

因为那个最热的 key 有 52 万条记录,而同一个 key 必然落在同一个 reducer 上。你加再多机器,它还是那一台在扛。

倾斜的本质是「单个 key 的数据量 」,而 reducer 数量只影响「key 怎么分组」。这是两个维度的事,加机器解决不了。

更糟的是:reducer 加多了,调度开销、小文件数量、shuffle 的元数据都在涨------成本上升,耗时不变。

三种解法,效果差很多

markdown 复制代码
方案                            最大/平均    相比不处理         额外代价
-------------------------------------------------------------------------
不处理                            17.02x        基准              ---
Map 端预聚合 (Combiner)            1.31x       13.01x   需要可结合的聚合函数
加盐 8 份 + 二次聚合                 4.85x        3.51x   多一轮 shuffle, 数据 ×8
加盐 32 份 + 二次聚合                2.36x        7.22x   多一轮 shuffle, 数据 ×32
加盐 128 份 + 二次聚合               1.50x       11.36x   多一轮 shuffle, 数据 ×128
剥离 top1 热点 key 单独算            9.42x        1.81x       热点占总量 26%
剥离 top10 热点 key 单独算           3.34x        5.10x       热点占总量 59%
剥离 top100 热点 key 单独算          1.84x        9.24x       热点占总量 80%

① Map 端预聚合(Combiner)------效果最好

17.02 → 1.31,改善 13 倍,而且几乎没有额外代价。

原理是在 shuffle 之前 就把同一个 key 的记录合并掉。每个 mapper 只为它见到的每个 key 输出一条部分结果,reducer 收到的数据量从 200 万降到了「mapper 数 × key 数」。

倾斜被消灭在了数据离开 map 端之前。

限制是聚合函数必须可结合、可交换 :sum、count、max、min 都行;avg 要拆成 (sum, count) 再算;中位数、精确去重不行(这也是前几天那篇讲 HyperLogLog 的文章的价值所在------它让「去重计数」变成了可合并的)。

能用 Combiner 就一定要用。 Spark 里 reduceByKey 自带 combiner,groupByKey 没有------这就是那条著名建议「别用 groupByKey」的全部原因。

② 加盐(两阶段聚合)

给 key 拼一个随机后缀,把一个热 key 强行拆成 N 份,聚合完再去掉盐做第二次聚合。

效果取决于盐的份数:8 份只改善 3.5 倍,128 份能到 11.4 倍。

代价是数据量翻 N 倍 (每个 key 变成 N 个),还要多一轮 shuffle。盐撒得越多效果越好,但代价也越大------需要调参,而且参数和数据分布强相关。

③ 剥离热点 key

先统计出 top-N 的热 key,这部分走特殊路径(比如广播 join、单独的大内存任务),其余走正常流程。

剥离 top1 只改善 1.81 倍------因为倾斜不是一个 key 造成的,是一小撮。要剥到 top100 才有 9.24 倍。

这个方案最灵活但也最脏:要先跑一次采样统计、要维护一个热 key 名单、业务逻辑要分叉。

怎么发现倾斜

倾斜最典型的症状是:99% 的 task 几秒钟跑完,剩下 1~2 个跑了一小时。

在 Spark UI 的 Stage 页面看 task 的耗时分布------如果 max 远大于 median,就是倾斜。还有几个信号:

  • 某个 task 反复 OOM,重试还是 OOM(数据量根本放不下)
  • shuffle read size 的分布极不均匀
  • 作业总时长远大于「总数据量 / 集群吞吐」的理论值

Spark 3.0+ 的 AQE(自适应查询执行)能自动处理 join 倾斜 (spark.sql.adaptive.skewJoin.enabled),它会在运行时发现某个分区过大并自动拆分。但它只管 join,聚合还得自己来。

实践清单

  1. 先看数据分布再写代码。 SELECT key, count(*) ... ORDER BY 2 DESC LIMIT 20 花一分钟,能省一天。
  2. 能用 Combiner / reduceByKey 就别用 groupByKey。 这是性价比最高的一条。
  3. 处理 NULL 和空值。 把它们过滤掉或者单独打散,往往能解决一半问题。
  4. 别靠加 reducer。 实测 256 倍机器换 8% 改善。
  5. 开 AQE。 免费的自动倾斜处理。
  6. 监控 task 耗时的 max/median 比值,而不只是总时长。

最后

分布式计算的性能模型,和单机完全不同。

单机看的是总工作量 ;分布式看的是最大的那一份。

而 Zipf 分布保证了:总会有那么一份,大得离谱。

相关推荐
没事写点啥1 小时前
WeKnora 问答实现
后端·agent·ai编程
五百万啊啊啊啊1 小时前
RSA+AES 混合加密的三个坑
后端
糖果豆豆1 小时前
监控全绿,线上还是抖了一下——加埋点只能解决三种"看不见"里的一种
后端·架构
琳达爱分享1 小时前
五个线程同时去换 token,四个被踢下线:刷新接口其实不是幂等的
后端·算法·架构
数据库小学妹1 小时前
MySQL表碎片与 OPTIMIZE TABLE:空间回收的代价与边界
数据库·后端·mysql
故图1 小时前
全字段 INSERT IGNORE:大约 70 行零日期,把证照列表加载打崩
后端
Json____1 小时前
用 FastAPI + Vue3 打造一套健身私教课预约管理系统
后端·fastapi·it学习·wwwoop.com
aibo8603082 小时前
基于 Spring Boot + Vue3 的博物馆藏品管理系统设计与实现
java·spring boot·后端
苍何2 小时前
Codex+Obsidian,多平台同步,一键分发,太香了!
后端