2023年冬天某个凌晨,我负责的 Flink Session 集群突然炸了:二十多个实时作业同时重启,重启之后再挂,集群状态像在跳机械舞。监控面板上一看,CPU 和网络都平静得不像话,TaskManager 的堆内存也健康,唯独 JobManager 进程在半小时前消失过一次,然后被资源管理器拉起来,跑了几分钟又没了。
最后捞 Yarn 日志,倒数几行写着 java.lang.OutOfMemoryError: Metaspace。那一刻我才意识到,在实时计算集群里,最容易被当作“不用管”的角色,恰恰是那个很少出现在内存告警面板上的 JobManager。很多人觉得它只是控制面,不搬数据、不存状态,内存给个 1GB 就够意思了,但现实是:当 Flink 作业数量一多、作业拓扑一复杂、上线下线一频繁,控制面会先于所有跑数据的节点倒下,而且是整个 Session 集群一起陪葬。
这篇文章不绕弯子,直接聊 JobManager 内存配置。内容包括 JobManager 到底把内存花在了哪里、Flink 1.11 之后的内存模型怎么拆、Session 和 Application 模式分别怎么给配置、怎么从一次 Metaspace OOM 里完整排查出根因,最后是一份我压过多次生产环境后总结的避坑清单。
1. 为什么“控制面”也会先挂:JobManager 的真实内存压力在哪
1.1 一场典型的“无声下线”故障复盘
那次故障的 Session 集群里有 40 多个作业,提交方来自不同业务组。JobManager 运行在 Yarn 的一个 4GB 容器里,-Xmx 只有 1GB,Metaspace 上限更低。最开始阶段一切正常,但随着业务组不断上线新作业、下线旧作业,集群上的作业存量慢慢堆到 40 个以上。
故障当天的导火索是某个数据团队一次性提交了 6 个 SQL 作业。从监控上看,作业提交后 JobManager 的 CPU 瞬间打满,三四秒后进程直接消失。这种场景我后来见过很多次:JobManager 的 OOM 往往不会像 TaskManager 那样先反压、再告警、最后才崩溃,它更像是“本来还在正常呼吸,下一秒直接没了”。如果告警只监控了 Yarn 的 App 状态,你可能只看到作业重启,根本不会第一时间联想到是 JobManager 的内存配置问题。
1.2 不搬数据不等于不吃内存
这里有个普遍误解:TaskManager 管数据、管状态、管网络 Buffer,所以它应该吃内存;JobManager 只是做调度和协调,内存压力应该很小。这个判断在单作业、小并行度的场景下勉强成立,但在多租户 Session 集群里完全不成立。
JobManager 虽然不搬业务数据,但它要做几件非常吃内存的事。第一,接收作业提交后,要把 JobGraph 转成 ExecutionGraph。这个转换过程会把一个算子的每个并行子任务都展开成独立的 Runtime 对象。一个只有几十个算子的作业,在 ExecutionGraph 层可能展开成几万个对象,对象之间还有复杂的父子引用关系。Session 集群里几十个作业的 ExecutionGraph 同时驻留在内存中,堆内存的消耗很可观。
第二,JobManager 负责所有任务的调度。每一次 failover、重启、重新调度,都会在堆上创建大量的调度请求、Slot 分配记录、部署描述对象。如果有多个作业同时 failover,短时间内的对象叠加十分惊人。
第三,CheckpointCoordinator 也在 JobManager 里。每个作业的 Checkpoint 进度、参与方列表、待确认的 Ack 状态都要在内存中维护。Sink 端写外部存储慢,或者反压导致 Checkpoint 迟迟无法完成,Pending 的 Checkpoint 会跟着堆积。
1.3 控制面内存的三个主要去向
我习惯把 JobManager 的内存去向总结成三个方向:
- 常驻作业元数据:JobGraph、ExecutionGraph、作业的 Class 信息、UDF 类加载器引用。这部分和“作业数量 × 作业复杂度”成正比,不随作业运行周期释放。
- 瞬时调度和协调对象:作业提交、任务部署、Checkpoint 协调、资源申请、RPC 消息处理。这部分和“作业重启频率、并发提交数量、TaskManager 节点规模”强相关。
- 类加载器和 JVM 元数据:每次提交一个新作业,如果带了不同的用户 JAR,JobManager 就需要创建对应的 ClassLoader 来加载用户代码。SQL 作业还会加载 Calcite 的元数据、表达式类。作业被下线后,如果 ClassLoader 被某些静态引用意外持有,Metaspace 就会泄漏。
理解这三部分来源后,再看 JobManager 的内存配置就清楚多了:不能只盯着堆大小,Metaspace 和堆外开销同样会导致进程死亡,而且它们在表面症状上很不一样。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
