一、什么是 Flink 背压?
先说一句最容易记住的话:
背压不是“下游慢了”这么简单,而是“上游生产得比下游消费得快,导致数据推不出去”。
在 Flink 里,数据是从上游流向下游的:
- Source → Map → Window → Sink
但是背压传播的方向,恰恰是反过来的:
- Sink 慢
- 会反压上游算子
- 上游再继续反压更上游
- 最后你可能在 Source 上看到 HIGH
这就是为什么很多人第一次看 Flink 背压会误判:
“Source 背压 94%,是不是 Source 有问题?”
其实不一定。
更有可能是:
- 下游某个窗口算子处理慢
- 某个 Sink 写出慢
- 某个分支卡住了
- 某些 key 特别热,导致部分 subtask 堵死
也就是说:
看到哪个算子背压高,不代表问题就出在它自己身上,它可能只是“被下游拖累”的受害者。
二、Flink 是怎么判断“背压”的?
Flink 内部判断背压,并不是靠“感觉”,而是有明确标准的。
其核心逻辑是:
- 如果一个 task 没有可用的输出缓冲区(output buffers)
- 那么这个 task 就会被认为是 back pressured
而 idle(空闲) 的判断则是:
- 当前没有可处理的输入
换句话说:
- Back pressured:有数据,但发不出去
- Idle:没数据可处理
- Busy:正在干活
三、最关键的 3 个指标:backPressured / idle / busy
Flink 每个并行实例(subtask)都会暴露 3 个核心指标:
- backPressuredTimeMsPerSecond
- idleTimeMsPerSecond
- busyTimeMsPerSecond
它们的含义分别是:
1)backPressuredTimeMsPerSecond
这个 subtask 在最近一段时间里,有多少时间处于“被背压”状态。
2)idleTimeMsPerSecond
这个 subtask 在最近一段时间里,有多少时间在“等活干”。
3)busyTimeMsPerSecond
这个 subtask 在最近一段时间里,有多少时间真正处于忙碌处理数据状态。
这 3 个值加起来,大约等于:
- 1000ms
也就是说,它们本质是在描述:
一个 subtask 在最近 1 秒时间里,分别有多少时间在“被堵住”“没活干”“忙着处理”。
这个设计特别好,因为它把任务状态拆得非常直观。
比如:
-
100% / 0% / 0%
说明:一直被背压,几乎完全堵住了 -
0% / 100% / 0%
说明:一直空闲,压根没数据 -
0% / 0% / 100%
说明:一直满负荷工作,但没有被堵 -
50% / 50% / 0%
说明:一半时间被堵,一半时间在等,没有真正处理太多数据
四、Flink Web UI 上颜色是什么意思?
Flink 在 JobGraph 里会把背压和忙碌程度做成颜色热力图,这一点对排障特别友好。
颜色大致可以这样理解:
- 蓝色:空闲(Idling)
- 黑色 / 深色:完全背压(Fully Back Pressured)
- 红色:完全忙碌(Fully Busy)
- 中间状态会用不同深浅混合表示
所以你在图上看到某个节点颜色特别深,并且背压值很高,往往就说明:
- 它正在被下游明显限制
五、背压状态怎么分级?
在 Web UI 的 Back Pressure 页签中,Flink 会给每个 subtask 一个状态:
- OK:0% <= back pressured <= 10%
- LOW:10% < back pressured <= 50%
- HIGH:50% < back pressured <= 100%
这个分级特别适合快速扫一眼现场:
- 一大片 OK:整体比较健康
- 零星 LOW:有轻微压力
- 一片 HIGH:基本可以确认瓶颈已经比较明显了
六、先看第一张图:JobGraph 一眼看出谁在“拖后腿”

图1:Flink JobGraph 背压热力图,直观看出不同算子的 backpressured / busy / watermark 状态
从这张图里,我们能直接看到几个非常关键的现象。
1)Source 背压高达 94%
左侧 Source 节点显示:
- Backpressured (max): 94%
这说明 Source 绝大多数时间都在“想发数据但发不出去”。
但这并不意味着 Source 自己有问题,更大的概率是:
- 下游消费不动
- 背压一路反传到了 Source
2)KeyedMapper 节点也有明显背压
中间这个节点显示:
- Backpressured (max): 68%
- Busy (max): 33%
这个状态很典型:
- 它不是完全空闲
- 也不是一直在忙
- 而是很大一部分时间都在被堵
这通常意味着:
它有数据,也有处理能力,但输出链路不畅。
3)上方分支基本健康
上方这条分支:
- TumblingWindowOperator:背压 0%,忙碌 38%
- MapReturnsOutputWithCustomStatefulTypeSerializer:背压 0%,忙碌 13%
这说明上方分支不是主要瓶颈。
4)下方 SlidingWindowOperator 更像热点区域
下方这个节点显示:
- SlidingWindowOperator
- Backpressured (max): 20%
- Busy (max): 82%
这个组合很有意思:
- 忙碌度很高
- 同时存在一定背压
这类节点非常值得重点怀疑,因为它往往意味着:
- 它确实在高负载处理
- 但处理速度已经开始跟不上了
- 或者它后面的链路也存在压力
5)最重要的判断:问题大概率不在上面,而在下方链路或更后面的消费端
结合整张图来看,可以得出一个非常有经验味道的结论:
- Source 背压很高
- 中间 mapper 背压明显
- 上方分支无明显背压
- 下方 sliding window 分支更忙、更可疑
所以这时候真正应该重点查的,不是 Source,而是:
- 下方 SlidingWindowOperator
- 或者它后面的 Sink
- 或者这条分支上的 key 分布是否不均衡
换句话说:
上游“喊疼”,不代表病灶在上游。真正的病灶,往往在更下游。
七、再看第二张图:SubTask 级别的背压明细,直接暴露不均衡问题

图2:Back Pressure SubTasks 明细页,不同 subtask 的 backpressured / idle / busy 差异一眼看穿
这张图比 JobGraph 更有价值,因为它把问题直接拆到了 subtask 级别。
页面顶部显示:
- Back Pressure Status: HIGH
说明这个算子的整体背压已经很明显。
但真正值得看的,是下面每个 subtask 的具体状态。
1)Subtask 0、1、7、8、9、10:100% 背压
例如:
- 100% / 0% / 0%
- 状态:HIGH
这说明这些 subtask 几乎一直处于:
- 有数据
- 想往下游发
- 但完全发不出去
这是非常严重的堵塞信号。
2)Subtask 2、3、4:0% 背压,100% 空闲
例如:
- 0% / 100% / 0%
- 状态:OK
这就特别有意思了。
同一个算子下,居然有些 subtask:
- 完全被堵死
而另一些 subtask:
- 完全没活干
这通常说明了一个非常典型的问题:
负载分布极不均衡。
最常见原因包括:
- keyBy 后 key 分布严重倾斜
- 某些热点 key 全部打到了少数 subtask
- 某些分区数据远高于其他分区
- 自定义分区策略不均衡
3)Subtask 5:49% 背压,51% 空闲
这是一个非常有代表性的“中间态”:
- 它既不是完全健康
- 也不是完全堵死
- 说明它处在临界状态
这种 subtask 往往是判断问题边界的好线索。
4)Subtask 6:97% 背压
这说明它几乎和那些 100% 的 subtask 一样,也被严重压住了。
八、这张 SubTask 图到底说明了什么?
如果只看整体状态 HIGH,你只能知道“有问题”。
但看完 subtask 明细后,你能知道的问题就完全不一样了:
结论 1:这不是“所有 subtask 一起慢”
因为有些 subtask 是:
- 0% / 100% / 0%
说明它们根本没在干活。
结论 2:这是非常明显的“局部拥堵”
有些 subtask 被压到 100%,有些却完全空闲,这说明瓶颈不是平均分布的,而是集中在少数并行实例上。
结论 3:高度怀疑数据倾斜 / 热 key / 分区不均
这类现象在 Flink 里特别常见于:
- keyBy 后热点 key 集中
- 某些窗口 key 特别热
- 某些分区数据远大于其他分区
- 自定义分区器分布不均匀
也就是说,这已经不是简单的“算子慢”,而更像是:
少数 subtask 被打爆,多数 subtask 在围观。
九、为什么很多人排查背压,总是查错方向?
因为他们只会看“哪个节点黑了”,不会看“黑的是不是所有 subtask”。
这是两个完全不同的排查维度。
情况一:所有 subtask 都很忙、都有背压
这更像是:
- 整体处理能力不足
- 下游整体吞吐太低
- 硬件 / IO / 状态后端 / Sink 能力瓶颈
情况二:只有少数 subtask 背压严重,其他 subtask 空闲
这更像是:
- 数据倾斜
- 热 key
- 分区不均
- 某些特定 key 路径特别重
而你这张图,明显更接近第二种。
十、看到背压后,正确排查顺序是什么?
很多人一看到背压就开始:
- 调并行度
- 调 checkpoint
- 调 buffer
- 调内存
这不是不行,但很容易“头痛医头,脚痛医脚”。
更推荐你按下面这个顺序排。
第一步:先看 JobGraph,确定背压传播方向
也就是先看:
- 谁最黑
- 谁最忙
- 哪条链路最可疑
你的图里很明显:
- Source 被压得最明显
- 但更可疑的是下方 sliding window 分支
第二步:进入 Back Pressure 页签,看 SubTask 明细
这是最关键的一步。
如果你不看 subtask,就永远无法判断:
- 是整体慢
- 还是局部热点
你的图里很明显是:
- 局部热点严重
第三步:结合上游分区方式和 keyBy 逻辑排查
这一步往往才是真正的根因定位点。
重点问自己几个问题:
- 是否做了 keyBy?
- key 的分布是否均匀?
- 是否存在热点 key?
- 是否某些窗口 key 极度集中?
- 是否分支逻辑导致一部分数据被路由到了少数 subtask?
第四步:再去看真正的业务算子是不是太重
如果 key 分布没问题,那再继续查:
- 窗口聚合是否太重
- 状态是否过大
- Sink 写出是否慢
- 外部系统(如 Redis / Kafka / ES / DB)是否跟不上
十一、一个非常实用的排障口诀
你可以直接把下面这段记住,基本够用了:
看到 Source 背压高
不要先怀疑 Source,先看下游。
看到某个算子 HIGH
不要只看算子级别,立刻下钻到 SubTask。
看到部分 subtask 100% 背压、部分 100% idle
优先怀疑:
- 热 key
- 数据倾斜
- 分区不均
看到所有 subtask 都很高
优先怀疑:
- 下游整体吞吐不足
- Sink 慢
- 状态后端慢
- 外部系统瓶颈
十二、结合这两张图,给出一个更像“实战复盘”的结论
如果这是我在线上值班时看到的现场,我会先下这样的判断:
结论一:当前任务存在明显背压,且已经传导到了 Source
Source 的 Backpressured (max): 94% 说明背压传播已经非常充分,问题不是刚刚开始,而是已经比较明显了。
结论二:真正可疑的不是上方分支,而是下方 SlidingWindow 分支
因为上方分支几乎没有背压,而下方分支:
- 忙碌度高
- 存在背压
- 更像瓶颈所在
结论三:SubTask 明细强烈暗示存在数据分布不均
部分 subtask:
- 100% 背压
部分 subtask:
- 100% 空闲
这不是“全链路慢”,而是“局部热点非常重”。
结论四:优先排查 keyBy 后的热点 key / 数据倾斜
这是当前最值得先下手的地方,而不是一上来就盲目加机器、加并行度。
因为如果根因是热点 key:
- 并行度加了,也未必有用
- 热点还是会继续落到少数 subtask 上
十三、背压优化,别只会“加并行度”
很多文章一谈优化就一句话:
- 加并行度
这当然有时有效,但并不是万能药。
更靠谱的优化方向通常有这些:
1)先解决数据倾斜
如果是热点 key:
- 优化 key 设计
- 做 key 打散
- 引入两阶段聚合
- 局部预聚合后再全局汇总
2)优化下游重算子
如果某个窗口、聚合、序列化逻辑特别重:
- 减少单条数据处理开销
- 优化状态访问
- 优化序列化
- 避免不必要的对象创建
3)优化 Sink
很多背压最终都卡在 Sink:
- Kafka sink flush 慢
- 数据库写入慢
- Elasticsearch bulk 慢
- 外部服务吞吐跟不上
4)看状态后端和 checkpoint
如果有状态算子很重,也可能通过:
- 优化 RocksDB 配置
- 降低状态膨胀
- 合理设置 checkpoint
- 避免频繁大状态写入
来缓解背压。
十四、最后总结:真正会看背压的人,不会只盯着“HIGH”
很多人看 Flink 背压,只会看一个词:
- HIGH
但真正有经验的人,会继续往下看两层:
第一层:是谁在被压?
也就是 JobGraph 上哪个节点在黑,背压是从哪条链路传上来的。
第二层:是全部 subtask 一起压,还是只有一部分在压?
这一步决定你是去查:
- 整体吞吐瓶颈
还是 - 局部热点 / 数据倾斜
而你这两张图,已经非常典型地展示了一个线上高频场景:
上游 Source 背压很高,但根因更可能在下游某条分支,且高度怀疑热点 key 或数据倾斜。
这也是 Flink 背压排查最容易踩的坑:
看到 Source 高,就去查 Source。
其实 Source 很多时候,只是在替真正的瓶颈“背锅”。



