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 反压的定位方法
- 看 Web UI 的 BackPressure 选项卡:红色高亮的就是瓶颈算子。
- 看线程栈:
jstack抓取 TaskManager 线程,搜索BackPressure关键字。 - 看指标:
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 怎么分内存的,不然排查时容易抓瞎。
4.3.2 实战排查步骤
遇到 OOM,别慌。按这个顺序来:
- 看异常栈:是 Heap OOM 还是 Direct Memory OOM?这决定了排查方向。
- 看 GC 日志:
-XX:+PrintGCDetails,看 Full GC 频率和老年代使用率。 - 看堆转储:用
jmap或jcmd生成 heap dump,用 MAT 分析大对象。 - 看状态大小: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 超时,Checkpoint 失败后状态无法清理,长期积累就 OOM。所以排查时别只看一个点,要顺着链路查下去。
我的习惯: 每次排查完,我都会把根因和修复方案记下来。时间长了,你会发现很多故障模式是重复的。比如“反压 + Checkpoint 超时 + 老年代缓慢增长”这个组合,十有八九是状态泄漏。