4. Flink 作业故障(上):Checkpoint 失败、反压问题、OOM 排查

实时特征平台跑着跑着突然挂了,这种事我遇到过太多次了。说实话,Flink 作业的故障类型虽然多,但最核心的就那么几个:Checkpoint 失败、反压、OOM。今天咱们就把这三个硬骨头啃下来。

核心观点: 这三个问题往往不是孤立的。反压可能导致 Checkpoint 超时,Checkpoint 失败可能加剧状态膨胀,最终引发 OOM。排查时要联动分析,别只看表面。

4.1 Checkpoint 失败:实时作业的“心跳”停了

Checkpoint 是 Flink 的命根子。它一旦失败,作业的容错能力就没了。我个人习惯先看 Checkpoint 的监控指标,而不是直接翻日志。

4.1.1 常见失败原因与排查路径

失败原因 典型现象 排查方向
Checkpoint 超时 监控中看到 Checkpoint expired before completing 检查反压、算子处理能力、网络延迟
状态后端写入失败 RocksDB 报错或 HDFS/S3 写入异常 检查存储集群负载、网络连通性、权限
Barrier 对齐失败 频繁出现 Checkpoint declined by task 检查数据倾斜、算子处理耗时波动
元数据不一致 JobManager 日志中有序列化异常 检查 UDF 或自定义状态是否可序列化

我在项目中遇到过最坑的一次,是某个 UDF 里用了不可序列化的对象。Checkpoint 一直失败,但日志里只报超时,查了两天才定位到。嗯,这里要注意:自定义状态和 UDF 一定要做序列化测试。

4.1.2 关键参数调优

别一上来就改参数。先确认问题类型,再对症下药。

# 调大超时时间(默认 10 分钟)
execution.checkpointing.timeout: 15min

# 开启非对齐 Checkpoint(适合反压场景)
execution.checkpointing.aligned-checkpoint-timeout: 30s
execution.checkpointing.unaligned: true

# 调整最小间隔,避免频繁触发
execution.checkpointing.min-pause-between-checkpoints: 500ms

我的经验: 非对齐 Checkpoint 不是银弹。它虽然能缓解反压下的超时问题,但会增加状态存储的 IO 开销。我一般只在反压持续超过 30 秒时才开启。

4.2 反压问题:数据堵在管道里了

反压说白了就是下游处理不过来,上游还在拼命发数据。你想想看,这就像高速公路出口堵了,入口还在放车进来,迟早全线瘫痪。

4.2.1 反压的定位方法

  1. 看 Web UI 的 BackPressure 选项卡:红色高亮的就是瓶颈算子。
  2. 看线程栈:jstack 抓取 TaskManager 线程,搜索 BackPressure 关键字。
  3. 看指标:busyTimeMsPerSecond 接近 1000ms 说明算子一直在忙。

我记得有一次,Web UI 上显示所有算子都是低反压,但作业就是慢。后来用 jstack 一看,有个自定义 Sink 在同步调用外部 API,线程全卡在 IO 等待上。这其实是隐式反压,UI 看不出来。

4.2.2 反压的常见原因与解法

原因 表现 解法
数据倾斜 某个 subtask 的 inputQueueLength 特别高 调整 KeyBy 策略,加盐或使用随机分区
算子处理能力不足 CPU 使用率打满,GC 频繁 优化算子逻辑,增加并行度
外部存储瓶颈 Sink 写入延迟高,连接池耗尽 使用异步 IO,增加缓冲批次
网络或磁盘 IO TaskManager 网络吞吐下降 检查网络带宽、磁盘读写速度

避坑指南: 我曾经遇到一个案例,反压是因为 RocksDB 的 write_buffer_size 设置太小,导致频繁刷盘。调大这个参数后,反压立刻缓解。但注意,调太大会增加 Checkpoint 状态大小,要平衡。

4.3 OOM 排查:内存去哪儿了?

OOM 是 Flink 作业最严重的故障之一。它通常不是突然发生的,而是内存泄漏或配置不当的累积结果。

4.3.1 Flink 内存模型速览

先搞清楚 Flink 怎么分内存的,不然排查时容易抓瞎。

Flink TaskManager 内存模型 JVM Heap Framework Heap Task Heap (算子状态、缓存) Managed Memory (RocksDB) Off-Heap Network Buffers Direct Memory (Netty) JVM Metaspace 总内存 常见 OOM 类型与对应区域 • Java Heap OOM → Task Heap 或 Managed Memory 溢出 • Direct Memory OOM → Network Buffer 或 Netty 泄漏 • Metaspace OOM → 类加载过多(常见于动态 UDF 加载) • 本地内存 OOM → RocksDB 未释放或 JNI 泄漏

4.3.2 实战排查步骤

遇到 OOM,别慌。按这个顺序来:

  1. 看异常栈:是 Heap OOM 还是 Direct Memory OOM?这决定了排查方向。
  2. 看 GC 日志:-XX:+PrintGCDetails,看 Full GC 频率和老年代使用率。
  3. 看堆转储:用 jmap 或 jcmd 生成 heap dump,用 MAT 分析大对象。
  4. 看状态大小:Flink Web UI 的 State Size 指标,确认是否状态膨胀。

关键命令:

# 生成堆转储(注意:会暂停 JVM,谨慎在高峰期执行)
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>

# 查看直接内存使用
jcmd <pid> VM.native_memory summary

# 查看 RocksDB 内存占用(通过 JMX 或指标)
curl http://taskmanager:port/metrics?get=rocksdb.cur-size-all-mem-tables

4.3.3 典型案例:状态泄漏

我曾经遇到一个作业,运行 3 天后准时 OOM。查堆转储发现,HashMapState 里堆积了上亿条过期数据。原因是 TTL 配置没生效——UDF 里直接操作了内部状态,绕过了 Flink 的 TTL 清理机制。

避坑指南: 使用 ValueState 或 MapState 时,一定要通过 StateTtlConfig 设置 TTL。别自己写清理逻辑,容易漏。我见过太多人手动 clear() 但忘记在 checkpoint 时持久化,结果恢复后数据又回来了。

4.4 三个问题的联动排查

这三个问题经常串在一起。我画了个排查流程图,帮你理清思路。

故障联动排查流程 作业异常告警 Checkpoint 失败? 检查反压 检查内存 联动分析:反压→CP失败→状态堆积→OOM

你看,反压会导致 Checkpoint 超时,Checkpoint 失败后状态无法清理,长期积累就 OOM。所以排查时别只看一个点,要顺着链路查下去。

我的习惯: 每次排查完,我都会把根因和修复方案记下来。时间长了,你会发现很多故障模式是重复的。比如“反压 + Checkpoint 超时 + 老年代缓慢增长”这个组合,十有八九是状态泄漏。