订阅更新

上游Kafka有数据倾斜,某个算子卡住了——Flink数据倾斜怎么解决?

Flink手册 第 4 题 约 3 分钟读完

本文目录
  1. 先说结论
  2. 怎么发现倾斜?
  3. 方案一:加盖打散两阶段聚合(最常用)
  4. 方案二:本地预聚合(Local KeyBy)
  5. 方案三:rebalance()打散数据
  6. 实战经验
  7. 总结

先说结论

Flink数据倾斜的解决思路和Spark类似:加盖打散两阶段聚合 > 本地预聚合 > 调整并行度。但在Flink里还有一个额外的手段:rebalance()打散数据。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

数据倾斜是所有大数据引擎的共同难题。Flink的倾斜和Spark类似但有一个关键区别:Flink是流处理,倾斜不会像Spark那样“跑完才发现慢”,而是直接导致反压,影响整个作业的吐吐量。今天把倾斜的发现、原因和解决方案讲清楚。

怎么发现倾斜?

在Flink Web UI的Task详情页,看每个Subtask的“Records Received”和“Records Sent”。如果多数Subtask收到100万条,但有一个Subtask收到了1000万条,那就是倾斜。

另外看反压情况:如果某个Subtask的反压指标为HIGH,其他是OK,基本可以确认是倾斜导致的。

方案一:加盖打散两阶段聚合(最常用)

和Spark的思路一样:先给Key加随机后缀做第一次聚合(打散),再去掉后缀做第二次聚合(汇总)。

// 第一次聚合:加盖打散
DataStream<Tuple2<String, Long>> partial = stream
.map(e -> {
String saltedKey = e.getCountry() + "_" + (e.hashCode() % 10);
return Tuple2.of(saltedKey, 1L);
})
.keyBy(t -> t.f0)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.sum(1);
 
// 第二次聚合:去盖汇总
DataStream<Tuple2<String, Long>> result = partial
.map(t -> Tuple2.of(t.f0.split("_")[0], t.f1))
.keyBy(t -> t.f0)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.sum(1);

方案二:本地预聚合(Local KeyBy)

在keyBy之前先在本地做一次聚合,减少传输到下游的数据量。这和Spark的reduceByKey在Map端做局部聚合是同一个思路。可以用Flink的ProcessFunction + 定时器实现局部缓存+定期刷出。

方案三:rebalance()打散数据

如果倾斜是因为Kafka分区不均匀导致的(而不是keyBy导致的),可以在keyBy前用rebalance()把数据均匀分配到所有Subtask。

// rebalance:轮询分配到所有下游Subtask
stream.rebalance()
.keyBy(e -> e.getCountry())
.window(...)
.aggregate(...);

实战经验

我在元宇宙社交App做实时礼物流水统计时,按user_id做keyBy。某个大V主播收的礼物占总量的40%,处理这个主播的Subtask严重反压。用加盖打散解决后,反压消失,吐吐量恢复正常。

总结

Flink倾斜的解决方案:加盖打散两阶段聚合(最常用)、本地预聚合、rebalance打散。通过Flink UI的Subtask的Records和反压指标发现倾斜。面试时能说清楚“怎么发现 + 多种解决方案 + 实战案例”,就很稳。

下一篇聊一个很常见的生产问题:Flink作业反压了,下游处理不过来——反压排查和解决思路。

你们生产环境遇到过Flink倾斜吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器