本文旨在详解 Flink TaskManager 的内存模型以及其各部分内存占比的计算逻辑。首先,结合官网展示了当前 Flink 的内存模型,并在之后结合 JVM 自身内存模型和管理机制结合讲解 Flink 内存模型的各个部分,最后结合源码解释了各部分内存占比的计算逻辑。
1 内存模型组成部分
本节摘自官网:https://nightlies.apache.org/flink/flink-docs-release-1.20/zh...
组成部分 | 配置参数 | 描述 |
---|---|---|
框架堆内存(Framework Heap Memory) | taskmanager.memory.framework.heap.size | 用于 Flink 框架的 JVM 堆内存(进阶配置)。 |
任务堆内存(Task Heap Memory) | taskmanager.memory.task.heap.size | 用于 Flink 应用的算子及用户代码的 JVM 堆内存。 |
托管内存(Managed memory) | taskmanager.memory.managed.size taskmanager.memory.managed.fraction | 由 Flink 管理的用于排序、哈希表、缓存中间结果及 RocksDB State Backend 的本地内存。 |
框架堆外内存(Framework Off-heap Memory) | taskmanager.memory.framework.off-heap.size | 用于 Flink 框架的堆外内存(直接内存或本地内存)(进阶配置)。 |
任务堆外内存(Task Off-heap Memory) | taskmanager.memory.task.off-heap.size | 用于 Flink 应用的算子及用户代码的堆外内存(直接内存或本地内存)。 |
网络内存(Network Memory) | taskmanager.memory.network.min taskmanager.memory.network.max taskmanager.memory.network.fraction | 用于任务之间数据传输的直接内存(例如网络传输缓冲)。该内存部分为基于 Flink 总内存的受限的等比内存部分。这块内存被用于分配网络缓冲 |
JVM Metaspace | taskmanager.memory.jvm-metaspace.size | Flink JVM 进程的 Metaspace。 |
JVM 开销 | taskmanager.memory.jvm-overhead.min taskmanager.memory.jvm-overhead.max taskmanager.memory.jvm-overhead.fraction | 用于其他 JVM 开销的本地内存,例如栈空间、垃圾回收空间等。该内存部分为基于进程总内存的受限的等比内存部分。 |
2 Flink 内存模型对内存的逻辑划分
Flink 作为一个 JVM 进程,首先复习一下 JVM 内存的简单划分:
- Heap memory:JVM 进程内部使用的内存,用于存储 java 对象,受 GC 控制;
- Native memory/Off-heap:Java 进程使用的用户地址空间上的非堆内存,不受 GC 控制,包含元空间(Metaspace)以及 Direct memory;
- Direct memory:Native Memory,意味着共享硬件内的底层缓冲区,目标是为了减少内存拷贝,可简单理解为 NIO 中提供的 allocate 方法获取的内存;
- Metaspace:Java8 后替代永久代的设计,之前类和方法、字符串常量池等信息存储在永久代,而永久代使用堆内存,容易出现 OOM,且增加了 GC 的复杂性,而 metaspace 使用 native memory,降低了该风险。
参考资料:
JVM 其实真正控制的内存部分就是堆内、堆外(Direct 部分)、元空间,严格来说元空间也属于堆外,但是 JVM 对这部分特别定义为了元空间。因此 Flink 在启动 TM 时可以利用 -XMs、-Xmx 等 JVM 配置控制堆内内存,利用 -XX:MaxDirectMemorySize 和 -XX:MaxMetaspaceSize 分别控制 direct memory 和元空间。其中 task off-heap、framework off-heap、network memory 这三部分均属于 direct memory。
而其他内存,比如通过 JNI 调用 C 代码申请的内存其实是不在 JVM 管控内的,因此这部分内存也没办法通过 JVM 的参数控制,比如 Flink 的内存模型中,jvm overhead 和 managed memory 这两部分内存是完全不受 JVM 管控的,属于 native memory(非 direct),只能通过容器或者系统对 JVM 整体进程进行监控 kill。
一般情况下,配置 Flink 内存最简单的方法是配置总内存。此外,Flink 也支持更细粒度的内存配置方式。
3 内存计算推断逻辑
3.1 推断入口及推断模式
TaskManager 的内存计算推断的逻辑入口在 org.apache.flink.runtime.clusterframework.TaskExecutorProcessUtils#processSpecFromConfig。
public static TaskExecutorProcessSpec processSpecFromConfig(final Configuration config) {
try {
return createMemoryProcessSpec(
config, PROCESS_MEMORY_UTILS.memoryProcessSpecFromConfig(config));
} catch (IllegalConfigurationException e) {
throw new IllegalConfigurationException(
"TaskManager memory configuration failed: " + e.getMessage(), e);
}
}
org.apache.flink.runtime.util.config.memory.ProcessMemoryUtils#memoryProcessSpecFromConfig 有三种推断模式(优先级自上而下):
public CommonProcessMemorySpec<FM> memoryProcessSpecFromConfig(Configuration config) {
if (options.getRequiredFineGrainedOptions().stream().allMatch(config::contains)) {
// all internal memory options are configured, use these to derive total Flink and
// process memory
return deriveProcessSpecWithExplicitInternalMemory(config);
} else if (config.contains(options.getTotalFlinkMemoryOption())) {
// internal memory options are not configured, total Flink memory is configured,
// derive from total flink memory
return deriveProcessSpecWithTotalFlinkMemory(config);
} else if (config.contains(options.getTotalProcessMemoryOption())) {
// total Flink memory is not configured, total process memory is configured,
// derive from total process memory
return deriveProcessSpecWithTotalProcessMemory(config);
}
return failBecauseRequiredOptionsNotConfigured();
}
如果以下部分的内存已经显式配置 size,进入
deriveProcessSpecWithExplicitInternalMemory
:taskmanager.memory.task.heap.size
taskmanager.memory.managed.size
- 显式配置了
taskmanager.memory.flink.size
,进入deriveProcessSpecWithTotalFlinkMemory
- 显式配置了
taskmanager.memory.process.size
,进入deriveProcessSpecWithTotalProcessMemory
3.2 deriveProcessSpecWithExplicitInternalMemory
private CommonProcessMemorySpec<FM> deriveProcessSpecWithExplicitInternalMemory(
Configuration config) {
FM flinkInternalMemory = flinkMemoryUtils.deriveFromRequiredFineGrainedOptions(config);
JvmMetaspaceAndOverhead jvmMetaspaceAndOverhead =
deriveJvmMetaspaceAndOverheadFromTotalFlinkMemory(
config, flinkInternalMemory.getTotalFlinkMemorySize());
return new CommonProcessMemorySpec<>(flinkInternalMemory, jvmMetaspaceAndOverhead);
}
- 根据各部分内存的细粒度配置推断整个 flink taskmanaged 的进程内存分配;
- 计算 jvm metaspace 和 jvm overhead 部分需要占用的内存 jvmMetaspaceAndOverhead。
3.3 deriveProcessSpecWithTotalFlinkMemory
private CommonProcessMemorySpec<FM> deriveProcessSpecWithTotalFlinkMemory(
Configuration config) {
MemorySize totalFlinkMemorySize =
getMemorySizeFromConfig(config, options.getTotalFlinkMemoryOption());
FM flinkInternalMemory =
flinkMemoryUtils.deriveFromTotalFlinkMemory(config, totalFlinkMemorySize);
JvmMetaspaceAndOverhead jvmMetaspaceAndOverhead =
deriveJvmMetaspaceAndOverheadFromTotalFlinkMemory(config, totalFlinkMemorySize);
return new CommonProcessMemorySpec<>(flinkInternalMemory, jvmMetaspaceAndOverhead);
}
- 根据配置获取 totalFlinkMemorySize;
- 根据 totalFlinkMemorySize 推断 flink taskmanager 进程各部分的内存分配: flinkInternalMemory;
- 计算 jvm metaspace 和 jvm overhead 部分需要占用的内存。
3.4 deriveProcessSpecWithTotalProcessMemory
private CommonProcessMemorySpec<FM> deriveProcessSpecWithTotalProcessMemory(
Configuration config) {
MemorySize totalProcessMemorySize =
getMemorySizeFromConfig(config, options.getTotalProcessMemoryOption());
JvmMetaspaceAndOverhead jvmMetaspaceAndOverhead =
deriveJvmMetaspaceAndOverheadWithTotalProcessMemory(config, totalProcessMemorySize);
MemorySize totalFlinkMemorySize =
totalProcessMemorySize.subtract(
jvmMetaspaceAndOverhead.getTotalJvmMetaspaceAndOverheadSize());
FM flinkInternalMemory =
flinkMemoryUtils.deriveFromTotalFlinkMemory(config, totalFlinkMemorySize);
return new CommonProcessMemorySpec<>(flinkInternalMemory, jvmMetaspaceAndOverhead);
}
- 根据配置获取 totalProcessMemorySize;
- 计算 jvm metaspace 和 jvm overhead 部分需要占用的内存 jvmMetaspaceAndOverhead;
- 计算得到 totalFlinkMemorySize = totalProcessMemorySize - jvmMetaspaceAndOverhead;
- 根据 totalFlinkMemorySize 推断 flink taskmanager 进程各部分的内存分配: flinkInternalMemory。
3.5 Jvm Metaspace 以及 overhead 部分推断
- 从配置中获取
taskmanager.memory.jvm-metaspace.size
的配置(默认为 256M); - 将 metaspace 这部分内存加到 totalFlinkMemorySize 中得到 totalFlinkAndJvmMetaspaceSize;
如果配置了总进程内存,则利用总进程和其他部分的内存情况计算 得到 jvm overhead 部分的内存:
jvmOverheadSize = totalProcessSize - totalFlinkMemorySize - jvmMetaspaceSize
- jvmOverheadSize。
如果未配置总进程内存,则利用 totalFlinkMemorySize + jvmMetaspaceSize 和
taskmanager.memory.jvm-overhead.fraction
的值进行相对计算:jvmOverheadSize = max(jvmOverheadMinSize, min(jvmOverheadMaxSize, (totalFlinkMemorySize + jvmMetaspaceSize) * (jvmOverHeadFraction /(1- jvmOverHeadFraction)))
3.6 Flink 各部分内存的详细计算逻辑(TaskExecutorFlinkMemoryUtils)
TaskExecutorFlinkMemoryUtils 提供了两个方法:
- 给定 flink memory size(不包含 jvm metaspace 和 overhead),推断内部的细粒度配置;
- 从细粒度的资源配置中推导出总的 flink memory size。
3.6.1 deriveFromTotalFlinkMemory
先计算几个较为固定的内存部分:
taskmanager.memory.framework.heap.size
,默认为 128 Mtaskmanager.memory.framework.off-heap.size
,默认为 128 Mtaskmanager.memory.task.off-heap.size
,默认为 0
检查是否显式配置了 taskmanager.memory.task.heap.size
,
如果配置了
- 计算 heap memory:从配置中获取
taskmanager.memory.task.heap.size
; - 计算 managed memory:先看
taskmanager.memory.managed.size
,如果未指定,则看taskmanager.memory.managed.fraction
(默认为 0.4):
total flink memory * taskmanager.memory.managed.fraction,并限制在 0~Long.MAX_VALUE 之间。 计算 network buffer memory:
network buffer memory = total flink memory - framework heap/off-heap memory - task heap/off-heap memory - managed memory
- 检查 network buffer memory 是否符合配置规范,比如在
taskmanager.memory.network.min
和taskmanager.memory.network.max
之间,且如果显式配置了taskmanager.memory.network.fraction
,则与该比例需要一致。
- 计算 heap memory:从配置中获取
如果没配置
- 计算 managed memory,和上述逻辑一致;
- 计算 network buffer memory,和上述计算 managed memory 逻辑一致,唯一区别是 task.manager.memory.network.fraction 的默认值为 0.1;
计算 heap memory:
heap memory = total flink memory - framework heap/off-heap memory - task off-heap memory - managed memory - network buffer memory
3.6.2 deriveFromRequiredFineGrainedOptions
如果方法名描述得那样,进入该方法的前提是 taskmanager.memory.task.heap.size
和taskmanager.memory.managed.size
均已显式配置。
- 先计算 heap memory 和 managed memory;
- 计算 framework heap memory 和 framework off-heap memory,默认为 128M;
- 计算 task off-heap memory,默认为 0;
- 检查是否显式配置了
taskmanager.memory.flink.size
: 如果配置了,计算
network buffer memory = total flink memory - framework heap/off-heap memory - task heap/off-heap memory - managed memory
并检查 network buffer memory 配置是否合理,见上文;
- 如果未配置,则优先通过配置计算 network buffer memory,再累加各部分内存得到 flink memory size。
4 内存配置优先级
从上述的计算逻辑可以看出来,大部分内存配置都有两种,一个是具体大小的配置,一个是比例配置(fraction),当同时指定二者时,会优先采用指定的大小(Size)。 若二者均未指定,会根据默认占比进行计算。
其次,内存的配置计算可以分为两类:
第一类,配置了多少就给多少,如果细粒度配置这部分给完计算发现超出总申请内存,则直接报错,这部分内存包含:
- framework heap memory;
- framework off-heap memory;
- managed memory;
- task off-heap memory;
第二类,根据配置和实际分配情况做一些微调:
- task heap memory;
- network memory。
如果配置 task heap memory,那么优先分配该块,剩下的内存分配给 network memory,否则则优先分配 network memory。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。