Flink 背压

一次订单宽表任务的排查与治理

Posted by shichaofaan on June 18, 2025

背压在 Flink 里不算罕见。WebUI 上一片红色,Lag 往上爬,Checkpoint 越来越慢,最后要么超时失败,要么结果延迟到业务受不了。概念大家都会说「下游处理不过来,向上游反压」,真正耗时间的是:谁在拖、为什么拖、能不能用局部手段扛住。

下面用一次真实一点的订单宽表任务,把排查路径和改法写清楚。

案例背景

任务大致链路:

Kafka(order_event) 
  -> JSON 解析 / 字段清洗 
  -> 维表异步 Join(用户、商品、店铺)
  -> 窗口聚合(1 分钟)
  -> 写入 JDBC upsert(结果库)

并行度 Source 12、中间算子 12、Sink 6。平时高峰 QPS 约 1.2 万,端到端延迟 3~8 秒。某次大促预热当晚,消费 Lag 半小时内从几千冲到 800 万+,Checkpoint 从稳定 8 秒变成频繁超过 1 分钟,最后超时失败重启,重启后又迅速背压。

业务侧表现:大屏订单量和实付金额明显滞后。

先看现象,再下结论

不要一上来改并行度。先固定看这几个位置:

  1. WebUI BackPressure
    哪个 SubTask 长期 HIGH / OK 交替,注意是「从哪个算子开始变红」。
  2. 火焰图 / Task 指标
    busyTimeMsPerSecond、idleTimeMsPerSecond、backPressuredTimeMsPerSecond。
  3. Checkpoint History
    对齐时间、同步时长、状态大小是否突然变大。
  4. Kafka Lag 分分区
    是全局堆积,还是某几个 partition 单点。

这次现象很典型:

  • Source 的 backPressuredTime 很高,但 busyTime 不高 → Source 本身不慢,是被下游堵住。
  • 维表 Join 算子 busy 接近打满,异步客户端 P99 从 20ms 飙到 400ms+。
  • Sink 也有背压,但幅度小于 Join;窗口算子状态大小当晚涨了一截。

结论初步指向:维表服务变慢是主因,窗口状态膨胀和 Sink 写入是放大器。

根因拆解

当晚维表(用户画像服务)做了一次扩容滚动发布,连接池短暂打满,超时重试被调高。Flink 侧 AsyncDataStream.unorderedWait 的超时是 3 秒,队列容量 1000。超时一多:

  1. 异步请求堆积,in-flight 打满。
  2. Join 算子处理不过来,向上游背压。
  3. Source 读 Kafka 变慢,Lag 上升。
  4. 事件时间窗口关不掉的数据更多,状态变大,Checkpoint 变重。
  5. Checkpoint 变重进一步占 IO / CPU,形成正反馈。

另外发现一个次要问题:Sink 并行度只有 6,JDBC 批量 batchSize=500,高峰时写入线程经常等待连接。主因不在 Sink,但它让恢复期更难「泄洪」。

治理动作(按优先级)

1. 先止血:降载 + 保护下游

临时把维表超时从 3s 调到 800ms,失败走侧输出写「待补全」Kafka,主链路先用默认值顶上。大促期间完整性可以后补,延迟不能继续恶化。

SingleOutputStreamOperator<OrderWide> joined = AsyncDataStream.unorderedWait(
    cleaned,
    new UserDimAsyncFunction(dimClient),
    800, TimeUnit.MILLISECONDS,
    2000  // 适当加大容量,避免动辄反压
);

joined.getSideOutput(DIM_MISS_TAG)
      .sinkTo(dimMissKafkaSink);

同时把结果 Sink 并行度临时提到 12,batchInterval 从 200ms 调到 100ms,加快泄洪。

2. 再治本:维表与缓存

维表服务恢复后,在 Flink 侧加了本地 LRU + 短 TTL(例如 60s)缓存热点用户。大促下单用户重复度高,缓存命中后异步调用量下来一大截。

注意:画像字段若要求强一致,TTL 不能太长;本次业务接受分钟级延迟,60s 可接受。

3. 控制窗口状态

检查发现 allowedLateness=2min 在大促期过长,叠加乱序,窗口状态峰值到平时 3 倍。把 lateness 收到 30s,迟到数据进侧输出异步修补。Checkpoint 大小回落到可接受范围。

4. 资源与并行度对齐

Sink 与中间算子并行度对齐到 12;TaskManager 网络缓冲按官方建议略调高(taskmanager.memory.network.fraction),避免短暂抖动就触发反压。没有证据前不要盲目加 TM 数量,先确认瓶颈算子。

怎么判断背压「治好了」

可以看一组对比:

指标 故障时 治理后高峰
Kafka Lag 800万+ < 5万,可消化
Join 算子 busyTime ~1000ms/s 600~750ms/s
Checkpoint 时长 经常 >60s 8~15s
端到端延迟 数分钟 10s 内

另外约定告警:backPressuredTimeMsPerSecond 连续 5 分钟 > 500,或 Lag 连续上涨超过阈值,先翻维表延迟和 Sink 写入 RT,而不是先扩容 Flink。

小结

背压是结果,不是根因。这条任务的根因是维表变慢,窗口状态和 Sink 并行度放大了影响。排查顺序建议固定成:

WebUI 谁在背压 → 该算子 busy/idle → 外部依赖 RT / 超时
→ Checkpoint 与状态大小 → Kafka 分区是否倾斜
→ 先降载止血,再缓存/并行度/语义参数治本

下次再遇到「一片红」,先问:是算不完,还是等外部;是全局慢,还是单点 partition / 单点 key。这两个问题答清楚,方向就不会偏。