4. 故障现象二:Checkpoint失败率过高(Failure Rate)——根因分析与排查步骤
Checkpoint 偶尔失败一两次,其实不算大事。系统自己会重试,重试几次也就过去了。
但如果你发现 失败率持续超过 10%,甚至每隔几分钟就报错一次——嗯,这就不是偶然了。我见过不少团队,线上告警天天响,一看 Checkpoint 失败率 30% 往上,作业还勉强能跑,但谁敢重启?一重启可能就起不来了。
这一章,我们就来拆解这个现象。我会把常见的根因归类,然后给出我自己的排查路径。
4.1 根因一:存储系统扛不住了
Checkpoint 的本质,是把状态数据写到远端存储。如果存储系统响应变慢,Barrier 对齐后数据写不进去,超时了就失败。
我个人习惯,遇到失败率飙升,第一件事就是看存储端的监控。不是看 Flink 日志,而是看 HDFS 或 S3 的延迟曲线。
| 存储类型 | 常见瓶颈 | 典型表现 |
|---|---|---|
| HDFS | NameNode 负载高、DataNode 磁盘 IO 打满 | 写入延迟 > 500ms,RPC 超时 |
| S3 / OSS | Bucket 限流、小文件过多 | HTTP 503 或 429 错误 |
| Kafka (changelog) | Topic 分区不足、副本同步慢 | Producer 发送超时 |
state.backend.local-recovery=true。这样即使远端存储抖动,TaskManager 本地还有一份缓存,能减少失败重试的代价。
4.2 根因二:网络成了瓶颈
说白了,Checkpoint 的数据传输路径是:TaskManager → 网络 → 远端存储。网络只要丢一个包,或者延迟突然升高,Barrier 对齐就会超时。
我记得有一次排查一个跨机房部署的作业,Checkpoint 失败率高达 40%。查了半天,发现是机房之间的专线带宽被另一个业务占满了。网络延迟从 2ms 飙到了 200ms。
排查命令(在 TaskManager 节点执行):
# 检查到存储节点的网络延迟
ping -c 10 <storage-host>
# 检查带宽占用
iftop -i eth0 -n
# 检查是否有丢包
netstat -s | grep -i "retransmit"
4.3 根因三:算子反压导致 Barrier 无法对齐
这是最常见、也最容易被忽略的原因。反压意味着某个算子处理不过来,数据在缓冲区堆积。Barrier 是跟着数据流走的,如果前面的数据堵住了,Barrier 过不去,Checkpoint 就会超时。
怎么判断?看 Flink Web UI 的 BackPressure 选项卡。如果某个算子显示 High,那基本就是它了。
我的排查步骤:
- 打开 Flink Web UI,找到反压高的算子。
- 查看该算子的
busyTimeMsPerSecond指标。如果持续 > 1000ms,说明 CPU 已经跑满了。 - 检查下游 Sink 的写入速度。很多时候是 Sink 写数据库太慢,把整条链路堵死了。
- 临时措施:增大并行度,或者调大
taskmanager.network.memory.buffer-debt-percent。
4.4 根因四:JVM GC 导致 Stop-The-World
这个比较隐蔽。GC 停顿时间如果超过 Checkpoint 的超时时间,Barrier 处理线程会被暂停,Checkpoint 自然就失败了。
我曾经遇到一个案例,作业状态量有 50GB,每次 Full GC 停顿 8 秒,而 Checkpoint 超时设置的是 5 分钟。按理说不会超时,但问题是 GC 频繁发生,导致 Barrier 对齐窗口反复被打断。
排查方法:
- 在 TaskManager 的 JVM 参数中添加
-XX:+PrintGCDetails -XX:+PrintGCDateStamps。 - 查看 GC 日志中 Full GC 的频率和停顿时间。
- 如果 Full GC 间隔小于 1 分钟,或者停顿时间 > 3 秒,就需要优化了。
优化方向:
- 使用
RocksDBStateBackend,它把状态放在堆外内存,GC 压力小很多。 - 调整
state.backend.rocksdb.memory.managed为 true,让 Flink 自动管理内存。 - 增大 TaskManager 的堆内存,给 GC 留出空间。
4.5 我的标准排查流程
上面说了四个根因,但实际排查时不能乱枪打鸟。我总结了一套流程,按顺序走,基本能定位 90% 的问题。
- 第一步:看监控大盘。 先确认是全局失败还是单个 TaskManager 失败。全局失败大概率是存储或网络问题。
- 第二步:看 Checkpoint 失败详情。 在 Web UI 点击失败的 Checkpoint,看具体报错。如果是
IOException,去查存储;如果是TimeoutException,去查网络或反压。 - 第三步:看反压状态。 如果某个算子反压高,先解决反压。反压不解决,调 Checkpoint 参数是没用的。
- 第四步:看 GC 日志。 如果前三步都没问题,再怀疑 GC。我见过太多人一上来就调 GC,结果发现是 HDFS 挂了。
- 第五步:看资源使用率。 CPU、内存、网络 IO 是否接近极限。如果是,考虑扩容。
checkpoint.timeout,结果失败率没降,反而因为超时时间变长,导致作业恢复更慢。记住:超时时间不是越大越好,它只是给了系统更多重试机会,根因不解决,调了也白调。
4.6 关键参数速查表
下面这几个参数,是排查高失败率时最常调整的。我列出来供你参考。
| 参数名 | 默认值 | 调整建议 |
|---|---|---|
execution.checkpointing.timeout |
10 min | 如果网络或存储偶尔抖动,可以适当增大到 15-20 min |
execution.checkpointing.max-concurrent-checkpoints |
1 | 不要轻易调大,会增加系统负载 |
execution.checkpointing.tolerable-failed-checkpoints |
0 | 生产环境建议设为 3-5,避免偶发失败导致作业重启 |
state.backend.local-recovery |
false | 建议开启,能减少存储抖动的影响 |
好了,这一章的内容就到这里。高失败率这个问题,说白了就是系统某个环节扛不住了。你只要按我上面说的流程,从存储、网络、反压、GC 四个方向逐一排查,大部分问题都能在 30 分钟内定位。