我们首先先从Flink的不同角色看一下Flink的内存管理,然后在微观的看一下Flink的内存管理器和数据模型,最后再观察下数据传输时的内存管理。
Flink内存概述
Flink的内存分为JobManager和TaskExecutor的内存管理。Flink总内存如右下图:JVM堆内存、非堆内存(直接内存、其他内存)
内存分类 | 作用 | 参数 |
---|---|---|
JVM堆内存 |
启动Flink框架所需内存
其他例如批提交ing时、checkpoint的执行等。
|
jobmanager.memory.heap.size |
堆外内存(直接内存) |
Akka的网络通讯
其他例如批提交ing时、checkpoint的执行等。
|
jobmanager.memory.off-heap.size |
JVM Metaspace |
存放JVM加载类的元数据
|
jobmanager.memory.jvm-metaspace.size |
JVM Overhead |
用于JVM其他操作开销:栈空间、垃圾回收空间等
|
jobmanager.memory.jvm-overhead.min |
jobManager的内存管理可以不用关注太多,因为这部分内存消耗起伏不会那么大。
如果遇到 JobManager 进程抛出 “OutOfMemoryError: Direct buffer memory” 的异常,可以尝试调大: jobmanager.memory.off-heap.size。
组成部分 | 配置参数 | 描述 |
---|---|---|
1.框架堆内存(Framework Heap Memory) | taskmanager.memory.framework.heap.size |
用于 Flink 框架的 JVM 堆内存(进阶配置)。 主要存放Flink runtime的一些对象,一般不会因为对象过多导致内存溢出,一般采用默认值即可(默认值 128M)。 |
2.任务堆内存(Task Heap Memory) | taskmanager.memory.task.heap.size |
Flink算子及用户代码(比如connector消费数据)的堆内存。堆内部分(Task Heap),无默认值,一般不建议设置,会自动用 Flink 总内存减去框架、托管、网络三部分的内存推算得出。 |
3.托管内存(Managed memory) | taskmanager.memory.managed.size taskmanager.memory.managed.fraction |
1. 由 Flink 管理的用于排序、哈希表、缓存中间结果及 RocksDB State Backend 的本地内存。
2. 纯堆外内存,由 MemoryManager 管理,用于中间结果缓存、排序、哈希表等,以及 RocksDB 状态后端,即RocksDB 消耗的内存可以由用户显式控制了。
|
4.框架堆外内存(Framework Off-heap Memory) | taskmanager.memory.framework.off-heap.size |
用于 Flink 框架的堆外内存(直接内存或本地内存)(进阶配置)。 |
5.任务堆外内存(Task Off-heap Memory) | taskmanager.memory.task.off-heap.size |
用于 Flink 应用的算子及用户代码的堆外内存(直接内存或本地内存)(比如用户代码使用netty进行数据传输)。 |
6.网络内存(Network Memory) | taskmanager.memory.network.min taskmanager.memory.network.max taskmanager.memory.network.fraction |
用于任务之间数据传输的直接内存(例如网络传输缓冲)。该内存部分为基于 Flink 总内存的受限的等比内存部分。这块内存被用于分配网络缓冲(比如不同taskmanager之间的数据传输) |
7.JVM Metaspace | taskmanager.memory.jvm-metaspace.size |
Flink JVM 进程的 Metaspace,(用于存放JVM加载类的元数据)配置比较固定。 |
8.JVM 开销 | taskmanager.memory.jvm-overhead.min taskmanager.memory.jvm-overhead.max taskmanager.memory.jvm-overhead.fraction |
用于其他 JVM 开销的本地内存,例如栈空间、垃圾回收空间等。该内存部分为基于进程总内存的受限的等比内存部分。 |
上面的配置还是很多的,但大概有几个清晰的内存功能:
内存分类 | 解释 |
---|---|
一. 堆内存 | |
1. 框架堆内存 | 启动TM所需内存 |
2. Task堆内存 | 存放、执行Flink算子及用户代码 |
二.堆外内存 | |
3. 框架堆外内存* | 用于 Flink 框架的堆外内存(直接内存或本地内存) |
4. 任务堆外内存* | 用于 Flink 应用的算子及用户代码的堆外内存(直接内存或本地内存)(比如用户代码使用netty进行数据传输)。 |
5. 网络内存* | 用户任务之间数据传输的直接内存 |
6. 托管内存 | 用于存放Flink的中间结果和RocksDB State Backend 的本地内存 |
7. JVM Metaspace和Overhead内存 | 用于JVM存储类元数据;JVM的例如栈空间、垃圾回收空间等开销 |
*代表直接内存。
名称 | 解释 |
---|---|
MemorySegment | |
内存页 |
访问MemorySegment内存数据的视图叫做内存页,数据读取为DataInputView,数据输出叫做DataOutputView。
|
Buffer |
MemorySegment的包装类。Task算子之间通过Buffer在网络传递数据,被分配的Buffer都必须得到释放才能重新使用。
一个NetworkBuffer包装了一个MemorySegment。Buffer的申请释放由NetworkBuffer管理。
|
Buffer资源池 |
LocalBufferPool实现了对(来自NetworkBufferPool的)Buffer管理,包括Buffer的申请、释放、销毁、可用Buffer通知等。
NetworkBufferPool用来对BufferPool的创建和销毁。
每个TaskManager只有一个NetworkBufferPool。TaskManager的tasks共享NetworkBufferPool
|
MemoryManager:用来管理 Flink 中用于排序、Hash 表、中间结果的缓存或使用堆外内存的状态后端(RocksDB)的内存。
1.10 之前版本,负责 TaskManager 所有内存。1.10 版本开始,管理范围是 Slot 级别。
简单看一下堆外内存申请的过程
//org.apache.flink.runtime.memory.MemoryManager
public void allocatePages(Object owner, Collection<MemorySegment> target, int numberOfPages)throws MemoryAllocationException {
。。。
allocatedSegments.compute(
owner,
(o, currentSegmentsForOwner) -> {
Set<MemorySegment> segmentsForOwner =
currentSegmentsForOwner == null
? new HashSet<>(numberOfPages)
: currentSegmentsForOwner;
for (long i = numberOfPages; i > 0; i--) {
//MemorySegment包装的内存块
//申请非堆内存:内存大小、owner、gc清理
MemorySegment segment =
allocateOffHeapUnsafeMemory(getPageSize(), owner, gcCleanup);
target.add(segment);
segmentsForOwner.add(segment);
}
return segmentsForOwner;
});
。。。
}
//org.apache.flink.core.memory.MemorySegmentFactory
public static MemorySegment allocateOffHeapUnsafeMemory(
int size, Object owner, Runnable gcCleanupAction) {
long address = MemoryUtils.allocateUnsafe(size);
//获取NIO相关的ByteBuffer
ByteBuffer offHeapBuffer = MemoryUtils.wrapUnsafeMemoryWithByteBuffer(address, size);
//创建垃圾获取器
Runnable cleaner =
MemoryUtils.createMemoryGcCleaner(offHeapBuffer, address, gcCleanupAction);
//创建非堆内存
return new HybridMemorySegment(offHeapBuffer, owner, false, cleaner);
}
释放指定段内存的过程
释放的内存段将回收到内存池
//org.apache.flink.runtime.memory.MemoryManager
public void release(MemorySegment segment) {
。。。
try {
allocatedSegments.computeIfPresent(
segment.getOwner(),
(o, segsForOwner) -> {
segment.free();
segsForOwner.remove(segment);
return segsForOwner.isEmpty() ? null : segsForOwner;
});
} catch (Throwable t) {
throw new RuntimeException(
"Error removing book-keeping reference to allocated memory segment.", t);
}
}
从宏观、内存概念的角度了解了Flink的内存模型后,接下来我们看下TaskManager在网络传输中的内存管理。
NetworkEnvironment、NetworkBufferPool等初始化
NetworkEnvironment 和 NetworkBufferPool 是 Task 之间共享的,每个 TM 只会实例化一个。
TM启动时会初始化NetworkEnvironment对象(用于管理TM中所有和网络相关的东西,比如NetworkBufferPool的管理)。
根据配置,Flink在NetworkBufferPool中生成一定数量(默认2048个)的内存块MemorySegment(默认大小:32KB)。
Task线程启动时,向NetworkEnvironment注册,env为Task的InputGate和ResultPartition分别创建一个(有一定的MemorySegment数量)LocalBufferPool。
应对流量瞬间压力
每当创建或销毁缓冲池时,NetworkBufferPool会计算剩余空闲的内存块数量,并平均分配给已创建的缓冲池。但只是指定没有真正的分配,当系统突发流量高峰时,可以释放剩余的内存块给压力大的缓冲池来应对流量压力。
inputGate、inputChannel、resultPartition、resultSubPartition概念简析
|
---|
当Task线程的Netty Channel收到数据时,InputChannel会向LocalBufferPool申请内存块,(当LocalBufferPool无可用内存块且没到申请上限,则会向 NetworkBufferPool 申请内存块,)申请到内存块后填数据到内存,然后消费内存块中的数据。
消费具体指的是:在输入端是指内存块中的字节被反序列化成对象,在输出端是指内存块中的字节写入到 Netty Channel 。
内存导致的反压
当LocalBufferPool已申请的内存数量达到上限(或NetworkBufferPool没有可用内存块)时,
对于写入:Task 的 Netty Channel会暂停读取,上游的发送端会立即响应停止发送,拓扑会进入反压状态。
对于写出:Task线程将结果写到RP,RP也会向LocalBufferPool请求内存块,没有可用的内存块,则会堵塞到请求内存块的地方。
当一个内存块被消费后,调用 Buffer.recycle() 方法,将内存块还给 LocalBufferPool 。
如果 LocalBufferPool 中当前申请的数量超过了池子容量,则LocalBufferPool 会将该内存块回收给 NetworkBufferPool。如果没超过池子容量,则会继续留在池子中,减少反复申请的开销。
参考:
https://zhuanlan.zhihu.com/p/427896176