当前位置:首页 > 科研成果库

大数据技术研发避坑实录:凌晨三点,我被一个Key卡住了脖子

2026-10-23 09:05:53小研科研成果库11

你见过凌晨三点的机房吗?我见过。不止见过,还对着屏幕骂过娘。

那是去年双十一大促前夜,一个跑批任务从晚上十点硬生生拖到凌晨三点还没结束——进度条像被焊死在99%。集群监控一片红,磁盘IO飙到天花板,可CPU利用率才不到20%。你说气不气人?

我盯着DAG图看了半小时,终于发现有个reduce任务的数据量是其他任务的几十倍。哦,又是数据倾斜。这玩意儿简直是大数据技术研发里的老油条,平时不显山露水,一到关键节点就跳出来扇你一巴掌。

今天不聊什么高大上的架构演进,就说说这个让人恨得牙痒痒的数据倾斜——它怎么来的、怎么治、以及那些年我交过的智商税。

深夜跑批,为什么一个Key就拖垮了整个集群?

先还原一下现场。我们当时做的是用户行为日志的聚合,按用户ID分组算点击次数。听起来很匀称对吧?几亿用户,每个用户行为数量差不多。但偏偏有那么几个“超级用户”——比如爬虫伪装的账号、或者内部压测产生的测试ID——它们贡献了上千万条记录。当这些记录被shuffle到同一个reduce分区时,那个分区就变成了大象,其他分区是蚂蚁。大象跑不动,蚂蚁干等着。

更操蛋的是,Spark的stage会等所有task完成才进入下一阶段。一个task卡住,整个作业就像堵在早高峰的十字路口。这时候你加内存?加CPU?没用——瓶颈在单点,不在总量。

Spark数据倾斜时各partition数据量柱状图对比Spark数据倾斜时各partition数据量柱状图对比

有次我尝试粗暴地调整partition数量,从200调到2000。心想着分得细一点总该均匀了吧?结果呢,那个超级Key还是在一个分区里——hash分区的逻辑没变,它永远落在同一个桶里。就像你把一堆苹果分到更多篮子里,但那个最大的苹果还是只能放一个篮子。蠢不蠢?我当时觉得自己蠢透了。

倾斜的本质:把“平均分”当成了理所当然

倾斜的本质:把“平均分”当成了理所当然倾斜的本质:把“平均分”当成了理所当然

很多人(包括曾经的我)下意识觉得:数据嘛,随机分布的。但现实世界的数据从来不是均匀的。用户活跃度符合幂律分布,商品销量有长尾效应,甚至网络流量都有重尾特征。大数据技术研发里最反直觉的一点是:你越假设数据是均匀的,系统就越容易在某个角落给你挖坑。

拿MapReduce或者Spark的shuffle来说,它默认用哈希函数把key映射到不同分区。理论很美——只要key足够随机,每个分区大小就差不多。可如果你的key本身就集中呢?比如按省份聚合,广东的数据量可能是西藏的几百倍;按设备型号聚合,某个爆款手机的占比能到30%。这还能指望哈希帮你抹平?做梦。

说白了,数据倾斜不是系统的bug,而是业务的feature。你非要让一个天生歪脖子的人去走直线,他不摔跤才怪。

不过话说回来,知道原因不等于能解决。我见过太多团队一遇到倾斜就三板斧:加资源、调并行度、换引擎。结果钱花了不少,问题依旧。为什么?因为没对症。

加盐、两阶段聚合,还有那些年踩过的坑

治疗倾斜的方法网上能搜出一箩筐:加随机前缀、拆分热点Key、两阶段聚合、map端join……但每个都有适用边界。我挑几个自己真刀真枪用过的说说。

第一招:给热点Key加盐。 比如原本key是“user_123”,我给它拼个随机后缀变成“user_123_0”到“user_123_9”,这样同一个用户的数据就散到10个分区里。聚合时先局部求和,再全局求和。听起来巧妙对吧?但注意——如果你的聚合逻辑是求平均值,那就不能简单加和,得把count和sum分开算。我第一次用的时候忘了这茬,结果统计出来的平均客单价离谱到姥姥家。被产品经理追着问了三天。

第二招:两阶段聚合。 先给key加随机前缀做一次聚合,去掉前缀后再做一次。原理类似,但更适合全局聚合场景。缺点是数据量翻倍——本来一次shuffle能搞定的事,现在要两次。所以集群资源得留够余量,不然你解决了倾斜,却制造了新的瓶颈。

大数据两阶段聚合解决数据倾斜的流程示意图大数据两阶段聚合解决数据倾斜的流程示意图

第三招:广播小表。 如果倾斜是因为join时大表关联小表,而且小表能塞进内存——那就别犹豫,直接broadcast。我见过有人硬扛着用reduce-side join,然后抱怨任务慢。兄弟,几MB的维度表broadcast出去,连shuffle都省了,不香吗?

但这里有个坑:广播的前提是“小表真的小”。有次我把一个200MB的表标记为广播,结果executor内存直接OOM。后来才明白,Spark广播变量会复制到每个executor,200MB乘以几十个executor就是几GB的开销。所以别光看表的大小,得算总账。

还有一招是调整partition数量配合自定义分区器,比如把热点key单独拎出来走特殊通道。这个需要你对业务数据有足够了解,知道哪些key是刺头。说实话,这更像手艺活——经验比理论管用。

不是所有歪脖子树都能掰直——说说边界和误区

写到这你可能觉得:哦,学了这几招就能包治百病了?想得美。

数据倾斜的治理是有天花板的。比如你需要全局排序,那reduce端必然要汇聚所有数据,倾斜躲不掉。或者业务逻辑就是按某个高频维度聚合,且无法拆分——那只能靠加资源硬抗,或者换用更弹性的计算引擎(比如Flink的增量聚合)。承认有些问题解决不了,比瞎折腾更重要。

另一个误区是过度优化。我见过团队为了一个每天只跑几分钟的任务,花两周调优倾斜。投入产出比呢?大数据技术研发的性价比,往往藏在“哪些问题不值得解决”的判断里。小任务慢就慢点,反正不耽误事。把精力留给那些卡住关键路径的作业。

还有一点容易被忽略:数据倾斜可能是业务设计问题的外溢。比如我们那个爬虫账号——如果能在数据接入层就过滤掉异常流量,后面哪来那么多破事?很多时候,往前端走一步,比在后端死磕十步都管用。

最后说个真事。上个月有个新人问我:“为啥不用采样预估分区大小然后动态拆分?”我反问他:“采样本身的开销算过吗?动态拆分引入的调度复杂度能hold住吗?”他沉默了。技术方案没有银弹,只有权衡。对吧?

凌晨三点那个任务最后怎么解决的?我把那个爬虫ID硬编码过滤了,然后给剩下的大客户ID单独开了条并行通道。土办法,但管用。有时候大数据技术研发就是这样——聪明人下笨功夫,比笨人下聪明功夫靠谱得多。

好了,天快亮了。如果你也在跟倾斜较劲,记住一句话:先搞清楚数据长啥样,再谈技术选型。别上来就调参,那跟蒙眼修车没区别。