Flink TaskManager 内存管理机制介绍与调优总结

71decc3a85b20ffe14ce509e10b31fbd.gif

作者:董伟柯,腾讯 CSIG 高级工程师

概要

Flink 的新版内存管理机制,要追溯到 2020 年初发布的 Flink 1.10 版本。当时 Flink 社区为了实现三大目标:

  1. 流和批模式下内存管理的统一,即同一套内存配置既可用于流作业也可用于批作业

  2. 管控好 RocksDB 等外部组件的内存,避免在容器环境下用量不受控导致被 KILL

  3. 消除不同部署模式下配置参数的歧义,消除 cut-off 等参数语义模糊的问题

提出了两个设计提案 FLIP-49: Unified Memory Configuration for TaskExecutors [1] 和 FLIP-116: Unified Memory Configuration for Job Managers [2],对之前 Flink 内存模型的各项缺陷进行了针对性的重构,为后续的流批一体演进奠定了基础。

由于这个版本距今已有两年多的历史,网上对其内存模型的解读文章也不胜枚举,他们有的对提案进行了中文化的翻译,有的则是对每个参数进行逐一讲解,帮助大家了解 Flink 的内存配置方法 [3] [4]。

本文则是上述简介文章的进一步延展:在新版内存管理模型的基础上,介绍每个区域的技术原理、相关技术资料,以及线上的调优经验,帮助大家在实际应用场景下,更好地规划 Flink 的内存空间,“知其然,也知其所以然”,提前识别和消除隐患。

TaskManager 内存分区总览

我们从 Flink 官网文档的 内存分区图 [5] 开始介绍 ,并加以批注:图的左边标注了每个区域的配置参数名,右边则是一个调优后的、使用 HashMapStateBackend 的作业内存各区域的容量限制:它和默认配置的区别在于 Managed Memory 部分被主动调整为 0,后面我们会讲解何时需要调整各区域的大小,以最大化利用内存空间。

6d5dc0a1d8686edebd24861119b58b9c.png

TaskManager 各内存区域详解

接下来,我们详细来看一下各个内存区域的含义、技术原理,以及 Flink 对它的默认值在什么场景下需要调整。

JVM 进程总内存(Total Process Memory)

该区域表示在容器环境下,TaskManager 所在 JVM 的最大可用的内存配额,包含了本文后续介绍的所有内存区域,超用时可能被强制结束进程。我们可以通过 taskmanager.memory.process.size 参数控制它的大小。

例如我们设置 JVM 进程总内存为 4G,TaskManager 运行在 Kubernetes 平台,则 Pod 配置的 spec -> resources -> limits -> memory 项会被设置为 4Gi,源码见 org.apache.flink.kubernetes.kubeclient.decorators.InitTaskManagerDecorator#decorateMainContainer,运行时的 YAML 配置如下图:

fd0fc4cd48cb0bee6ee629697e918714.png

而对于 YARN,如果 yarn.nodemanager.pmem-check-enabled 设为 true, 则也会在运行时定期检查容器内的进程是否超用内存。

如果进程总内存用量超出配额,容器平台通常会直接发送最严格的 SIGKILL 信号(相当于 kill -9)来中止 TaskManager,此时不会有任何延期退出的机会,可能会造成作业崩溃重启、外部系统资源无法释放等严重后果。

因此,在 有硬性资源配额检查 的容器环境下,请务必妥善设置该参数,对作业充分压测后,尽可能预留一部分安全余量,避免 TaskManager 频繁被 KILL 而导致的作业频繁重启。

Flink 总内存(Total Flink Memory)

该内存区域指的是 Flink 可以控制的内存区域,即上述提到的 JVM 进程总内存 减去 Flink 无法控制的 Metaspace(元空间)和 Overhead(运行时开销)区域。Flink 随后又把这部分内存区域划分为堆内、堆外(Direct)、堆外(Managed)等不同子区域,后面我们会逐一讲解他们的配置指南。

对于没有硬性资源限制的环境,我们建议使用 taskmanager.memory.flink.size 参数来配置 Flink 总内存的大小,然后 Flink 自己也会会自动根据参数,计算得到各个子区域的配额。如果作业运行正常,则无需单独调整。

例如 4G 的 进程总内存 配置下,JVM 运行时开销(Overhead)占 进程总内存 的 10% 但最多 1G(下图是 409.6M),元空间(Metaspace)占 256M;堆外直接(Direct)内存网络缓存占 Flink 总内存 的 10% 但最多 1G(下图是 343M),框架堆和框架堆外各占 128M,堆外管控(Managed)内存占 Flink 总内存 的 40%(下图是 1372M 即 1.34G),其他空间留给任务堆,即用户程序代码可以使用的内存空间(1459M 即 1.42G),我们接下来会讲到它。

b8ea6323cbb80cb9860e0ec931a4b2d6.png

JVM 堆内存(JVM Heap Memory)

堆内存大家想必都不陌生,它是由 JVM 提供给用户程序运行的内存区域,JVM 会按需运行 GC(垃圾回收器),协助清理失效对象。

当任务启动时,ProcessMemoryUtils#generateJvmParametersStr 方法会通过 -Xmx -Xms 参数设置堆内存的最大容量。

Flink 将堆内存从逻辑上划分为 “框架堆”、“任务堆” 两个子区域,分别通过 taskmanager.memory.framework.heap.sizetaskmanager.memory.task.heap.size 来指定其大小:框架堆默认是 128m,任务堆如果未显式设置其大小,则会通过扣减其他区域配额来计算得到。例如对于 4G 的进程总内存,扣除了其他区域后,任务堆可用的只有不到 1.5G。

但需要注意的是,Flink 自身并不能精确控制框架自身及任务会用多少堆内存,因此上述配置项只提供理论上的计算依据。如果实际用量超出配额,且 JVM 难以回收对象释放空间,则会抛出 OutOfMemoryError,此时 Flink TaskManager 会退出,导致作业崩溃重启。因此对于堆内存的监控是必须要配置的,当堆内存用量超过一定比率,或者 Full GC 时长和次数明显增长时,需要尽快介入并考虑扩容。

高级内容:对于使用 HashMapStateBackend(旧版本称之为 FileSystem StateBackend)的流作业用户,如果在进程总内存固定的前提下,希望尽可能提升任务堆的空间,则可以减少 托管内存(Managed Memory)的比例。我们接下来也会讲到它。

JVM 堆外内存(JVM Off-Heap Memory)

广义上的 堆外内存 指的是 JVM 堆之外的内存空间,而我们这里特指 JVM 进程总内存除了元空间(Metaspace)和运行时开销(Overhead)以外的内存区域。因为上述两个区域是 JVM 自行管理,Flink 无法介入,我们后面单独划分和讲解。

托管内存(Managed Memory)

文章开头的总览图中,把托管内存区域设为 0,此时任务堆空间约 3G;而使用 Flink 默认配置时,任务堆只有 1.5G。这是因为默认情况下,托管内存占了 40% 的 Flink 总内存,导致堆内存可用的量变的相当少。因此我们非常有必要了解什么是托管内存。

从官方文档和 Flink 源码上来看,托管内存主要有三大使用场景:

  1. 批处理算法,例如排序、HashJoin 等。他们会从 Flink 的 MemoryManager 请求内存片段(MemorySegment),而 MemoryManager 则会调用 UNSAFE.allocateMemory 分配堆外内存。

  2. RocksDB StateBackend,Flink 只会预留一部分空间并扣除预算,但是不介入实际内存分配。因此该类型的内存资源被称为 OpaqueMemoryResource. 实际的内存分配还是由 JNI 调用的 RocksDB 自己通过 malloc 函数申请。

  3. PyFlink。与 JNI 类似,在与 Python 进程交互的过程中,也会用到一部分托管内存。

显然,对于普通的流式 SQL 作业,如果启用了 RocksDB 状态后端时,才会大量使用托管内存。因此如果您的业务场景并未用到 RocksDB,那么可以调小托管内存的相对比例(taskmanager.memory.managed.fraction)或绝对大小(taskmanager.memory.managed.size),以增大任务堆的空间。

对于 RocksDB 作业,之所以分配了 40% Flink 总内存,是因为 RocksDB 的内存用量实在是一个很头疼的问题。早在 2017 年,就有 FLINK-7289: Memory allocation of RocksDB can be problematic in container environments [6] 这个问题单,随后社区对此做了大量的工作(通过 LRUCache 参数、增强 WriteBufferManager 的 Slot 内空间复用等),来尽可能地限制 RocksDB 的总内存用量。在我之前的 Flink on RocksDB 参数调优指南 [7] 文章中,也有提到 RocksDB 内存调优的各项参数,其中 MemTable、Block Cache 都是托管内存空间的用量大户。

为了避免手动调优的繁杂,Flink 新版内存管理默认将 state.backend.rocksdb.memory.managed 参数设为 true,这样就由 Flink 来计算 RocksDB 各部分需要用多少内存 [8],这也是 ”托管“ 的含义所在。如果仍然希望精细化手动调整 RocksDB 参数,则需要将上述参数设为 false.

直接内存(Direct Memory)

直接内存是 JVM 堆外的一类内存,它提供了相对安全可控但又不受 GC 影响的空间,JVM 参数是 -XX:MaxDirectMemorySize. 它主要用于

  1. 框架自身(taskmanager.memory.framework.off-heap.size 参数,默认 128M,例如 Sort-Merge Shuffle 算法所需的内存)

  2. 用户任务(taskmanager.memory.task.off-heap.size 参数,默认设为 0)

  3. Netty 对 Network Buffer 的网络传输(taskmanager.memory.network.fraction 等参数,默认 0.1 即 10% 的 Flink 总内存)。

在生产环境中,如果作业并行度非常大(例如大于 500 甚至 1000),则需要调大 taskmanager.network.memory.floating-buffers-per-gate(例如从 8 调整到 1000)和 taskmanager.network.memory.buffers-per-channel(例如从 2 调整到 500),避免 Network Buffer 不足导致作业报错。

JVM 元空间(JVM Metaspace)

JVM Metaspace 主要保存了加载的类和方法的元数据,Flink 配置的参数是 taskmanager.memory.jvm-metaspace.size,默认大小为 256M,JVM 参数是 -XX:MaxMetaspaceSize.

如果用户编写的 Flink 程序中,有大量的动态类加载的需求,例如我们之前遇到过一个用户作业,动态编译并加载了 44 万个类,此时就容易出现元空间用量远超预期,发生 OOM 报错。此时就需要适当调大元空间的大小,或者优化用户程序,及时卸载无用的 Classloader。

JVM 运行时开销(JVM Overhead)

除了上述描述的内存区域外,JVM 自己还有一小块 “自留地”,用来存放线程栈、编译的代码缓存、JNI 调用的库所分配的内存等等,Flink 配置参数是 taskmanager.memory.jvm-overhead.fraction,默认是 JVM 总内存的 10%。

对于旧版本(1.9 及之前)的 Flink,RocksDB 通过 malloc 分配的内存也属于 Overhead 部分,而新版 Flink 把这部分归类到托管内存(Managed),但由于 FLINK-15532 Enable strict capacity limit for memory usage for RocksDB [9] 问题仍未解决,RocksDB 仍然会少量超用一部分内存。

因此在生产环境下,如果 RocksDB 频繁造成内存超用,除了调大 Managed 托管内存外,也可以考虑调大 Overhead 区空间,以留出更多的安全余量。

参考阅读

[1] https://cwiki.apache.org/confluence/display/FLINK/FLIP-49%3A+Unified+Memory+Configuration+for+TaskExecutors

[2] https://cwiki.apache.org/confluence/display/FLINK/FLIP-116%3A+Unified+Memory+Configuration+for+Job+Managers

[3] https://www.jianshu.com/p/96364463c831

[4] https://zhuanlan.zhihu.com/p/141120042

[5] https://nightlies.apache.org/flink/flink-docs-master/zh/docs/deployment/memory/mem_setup_tm/#%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b%e8%af%a6%e8%a7%a3

[6] https://issues.apache.org/jira/browse/FLINK-7289

[7] https://cloud.tencent.com/developer/article/1592441

[8] https://www.jianshu.com/p/47a40259a450

[9] https://issues.apache.org/jira/browse/FLINK-15532

<think>好的,用户现在想了解如何在Fink的YARN Session模式下设置TaskManagerJVM内存大小。首先,我需要回想一下之前用户的问题和我的回答。之前用户询问了JobManager和TaskManager日志的区别,现在转向了资源配置,尤其是内存设置。这说明用户可能正在实际部署Flink应用,遇到了性能的问题,或者正在准备环境配置。 接下来,我需要确认Flink在YARN Session模式下的配置方式。YARN作为资源管理框架,Flink作为其上的应用,需要遵循YARN的资源管理机制。YARN负责分配容器,每个TaskManager对应一个容器,容器的资源包括内存和CPU。Flink的配置参数需要YARN的资源配置协,否则可能出现资源不足或浪费的情况。 用户的问题集中在TaskManagerJVM内存设置。这里有几个关键点:首先,Flink的内存模型包括总内存、JVM堆内存、堆外内存等部分。其次,在YARN模式下,需要同时设置Flink的参数和YARN的资源请求参数,因为YARN会根据请求分配容器资源。如果只设置Flink参数而忽略YARN的配置,可能会导致容器内存不足,从而引发OOM错误。 我需要拆解配置步骤。首先,确定TaskManager的总内存,这包括JVM堆内存、堆外内存、网络缓冲、框架堆外等部分。然后,通过Flink的配置文件或启动参数设置这些值。同时,YARN的容器内存需要至少等于TaskManager的总内存,否则YARN会杀死超用内存的容器。 常见的参数包括taskmanager.memory.process.size(或taskmanager.memory.flink.size,取决于版本),这个参数决定了Flink认为TaskManager需要的内存总量。同时,YARN的参数yarn.containers.vcores和yarn.container.memory需要对应设置,确保YARN分配的容器足够大。 另外,需要注意不同Flink版本之间的差异。例如,1.10之后的版本内存配置有所变化,引入了更细粒度的内存控制。用户可能使用的是较新版本,因此需要确认参数的正确性。同时,堆外内存的设置对于某些应用(如大量网络传输或使用原生内存的组件)非常重要,需要合理分配,避免OOM。 用户可能的误区是只设置JVM堆内存,而忽略了总内存和其他部分,导致YARN容器内存不足。因此,在回答中需要强同时配置Flink和YARN参数的重要性,并提供示例配置,帮助用户理解各部分的关系。此外,常见问题如内存分配不足、参数冲突等也需要提及,帮助用户避免典型错误。 最后,应该总结步骤,强验证方法,比如检查YARN的Web UI确认容器资源,或者查看日志中的内存分配信息,确保配置生效。这样用户可以实际操作并验证他们的配置是否正确。</think>在 Flink 的 **YARN Session 模式**下,设置 TaskManagerJVM 内存分配大小需要通过 Flink 的配置参数和 YARN 的资源请求参数协同完成。以下是具体配置方法和原理说明: --- ### 一、核心配置参数 FlinkTaskManager 内存由多个部分组成,需通过以下参数定义: | **参数名** | **作用** | |------------------------------------|-------------------------------------------------------------------------| | `taskmanager.memory.process.size` | **总内存**(JVM进程总内存,包括堆内外内存、网络缓冲、框架内存等,必须 YARN 容器内存一致) | | `taskmanager.memory.jvm-metaspace` | JVM Metaspace 内存大小(默认 256MB) | | `taskmanager.memory.jvm-overhead` | JVM 开销内存(用于线程栈、本地内存等,默认总内存的 10%) | --- ### 二、配置步骤(YARN Session 模式) #### 1. **设置 Flink 配置文件(`flink-conf.yaml`)** 在 `flink-conf.yaml` 中直接定义 TaskManager 的内存参数: ```yaml # 设置 TaskManager 总内存(必须 <= YARN容器内存) taskmanager.memory.process.size: 4096m # 4GB # 可选:整堆内存(默认总内存的约40%~50%) taskmanager.memory.jvm.heap.size: 2048m # 2GB # 可选:整堆外内存(Direct Memory,用于网络缓冲等) taskmanager.memory.managed.fraction: 0.3 # 托管内存占比(默认0.4) ``` #### 2. **启动 YARN Session 时指定资源** 在启动命令中通过 `-D` 参数覆盖配置,并指定 YARN 容器的资源: ```bash ./bin/yarn-session.sh \ -D taskmanager.memory.process.size=4096m \ -D yarn.containers.vcores=2 \ # 每个容器的vCPU数量 -D yarn.container.memory=4096 \ # 每个容器的总内存(单位MB,必须 >= taskmanager.memory.process.size) -n 4 # 启动的TaskManager数量 ``` --- ### 三、内存分配逻辑 #### 1. **内存组成公式** Flink 会按以下规则分配内存: $$ \text{总内存} = \text{堆内存} + \text{堆外内存} + \text{网络缓冲} + \text{Metaspace} + \text{JVM开销} $$ #### 2. **参数先级** - 如果显式指定 `taskmanager.memory.jvm.heap.size`,堆内存按指定值分配。 - 否则,堆内存按总内存的默认比例计算(例如:总内存的 40%)。 --- ### 四、验证配置是否生效 #### 1. **通过 YARN Web UI 检查** 访问 YARN ResourceManager Web UI(如 `http://<yarn-rm-host>:8088`),查看容器分配的内存是否配置一致。 #### 2. **查看 TaskManager 日志** 在 TaskManager 启动日志中搜索以下内容: ```log INFO org.apache.flink.runtime.taskexecutor.TaskManagerRunner - Starting TaskManager with ResourceID: {...} JVM Memory Settings: - Total Process Memory: 4096mb - JVM Heap Size: 2048mb - Managed Memory Size: 1228mb ... ``` --- ### 五、常见问题建议 1. **容器被 YARN 杀死(OOM)** - 原因:`yarn.container.memory` < `taskmanager.memory.process.size`。 - 解决:确保 YARN 容器内存 >= Flink 配置的总内存。 2. **堆外内存不足(`OutOfMemoryError: Direct buffer memory`)** - 原因:网络缓冲或 RocksDB 使用过多 Direct Memory。 - 解决:增大 `taskmanager.memory.process.size` 并整 `taskmanager.memory.managed.fraction`。 3. **Metaspace 溢出** - 原因:动态加载类过多(如频繁部署作业)。 - 解决:显式设置 `taskmanager.memory.jvm-metaspace.size=512m`。 --- ### 六、完整配置示例 ```yaml # flink-conf.yaml taskmanager.memory.process.size: 8192m # 总内存 8GB taskmanager.memory.jvm.heap.size: 4096m # 堆内存 4GB taskmanager.memory.managed.fraction: 0.4 # 托管内存占40% taskmanager.memory.jvm-metaspace.size: 512m taskmanager.memory.jvm-overhead.min: 512m ``` 启动命令: ```bash ./bin/yarn-session.sh \ -D yarn.container.memory=8192 \ -D yarn.containers.vcores=4 \ -n 6 ``` --- ### 总结 - **核心原则**:确保 YARN 容器内存 >= Flink TaskManager 配置的总内存。 - **重点**:根据作业特性(如批处理/流处理、状态后端类型)平衡堆内存堆外内存。 - **验证手段**:通过 YARN UI 和 TaskManager 日志双重确认配置生效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值