【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理

我们首先先从Flink的不同角色看一下Flink的内存管理,然后在微观的看一下Flink的内存管理器和数据模型,最后再观察下数据传输时的内存管理。

文章目录

  • 一、Flink内存模型
    • 1.Jobmanager的内存模型
      • 1.1. 内存分类
      • 1.2. 场景
    • 2.TaskManager的内存模型
      • 2.1. 内存分类
  • 二、Flink内存结构
    • 1. 内存结构成员分类
    • 2. 内存管理器-MemoryManager
  • 三、数据模型初始化与数据传输
    • 1. TM内存的初始化
    • 2. Task线程启动
    • 3. Task线程执行
      • 3.1. Task内存块申请和消费
      • 3.2. 内存块回收

一、Flink内存模型

Flink内存概述
Flink的内存分为JobManager和TaskExecutor的内存管理。Flink总内存如右下图:JVM堆内存、非堆内存(直接内存、其他内存)
【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第1张图片

 

1.Jobmanager的内存模型

1.1. 内存分类

内存分类 作用 参数
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的内存管理可以不用关注太多,因为这部分内存消耗起伏不会那么大。

【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第2张图片
 

1.2. 场景

如果遇到 JobManager 进程抛出 “OutOfMemoryError: Direct buffer memory” 的异常,可以尝试调大: jobmanager.memory.off-heap.size。

 

2.TaskManager的内存模型

2.1. 内存分类

【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第3张图片

 

组成部分 配置参数 描述
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的例如栈空间、垃圾回收空间等开销

*代表直接内存。

 
 

二、Flink内存结构

1. 内存结构成员分类

名称 解释
MemorySegment
Flink内存的分配单元为MemorySegment,默认大小32KB。它可以指堆内存(HeapMemorySegment)和堆外内存(HybridMemorySegment,基于 Netty 的 DirectByteBuffer)。
2017 年以后的版本实际上只使用了 HybridMemorySegment。 【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第4张图片
内存页
访问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

 
 

2. 内存管理器-MemoryManager

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在网络传输中的内存管理。
 

1. TM内存的初始化

NetworkEnvironment、NetworkBufferPool等初始化
NetworkEnvironment 和 NetworkBufferPool 是 Task 之间共享的,每个 TM 只会实例化一个。

TM启动时会初始化NetworkEnvironment对象(用于管理TM中所有和网络相关的东西,比如NetworkBufferPool的管理)。

根据配置,Flink在NetworkBufferPool中生成一定数量(默认2048个)的内存块MemorySegment(默认大小:32KB)。

 

2. Task线程启动

Task线程启动时,向NetworkEnvironment注册,env为Task的InputGate和ResultPartition分别创建一个(有一定的MemorySegment数量)LocalBufferPool。


应对流量瞬间压力
每当创建或销毁缓冲池时,NetworkBufferPool会计算剩余空闲的内存块数量,并平均分配给已创建的缓冲池。但只是指定没有真正的分配,当系统突发流量高峰时,可以释放剩余的内存块给压力大的缓冲池来应对流量压力。

 

inputGate、inputChannel、resultPartition、resultSubPartition概念简析
inputGate的作用

inputGate消费其上游算子(计算后)产生的中间结果,如下MapReduce过程: 在这里插入图片描述
当多个线程跑这个任务时,中间结果将划分到并行的子任务上。如下图:reduce1的InputGate消费(Map1的子分区1)+(Map2的子分区1)数据,然后数据交由reduce算子去消费数据。reduce2类似。 【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第5张图片 【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第6张图片

 

3. Task线程执行

3.1. Task内存块申请和消费

当Task线程的Netty Channel收到数据时,InputChannel会向LocalBufferPool申请内存块,(当LocalBufferPool无可用内存块且没到申请上限,则会向 NetworkBufferPool 申请内存块,)申请到内存块后填数据到内存,然后消费内存块中的数据。

消费具体指的是:在输入端是指内存块中的字节被反序列化成对象,在输出端是指内存块中的字节写入到 Netty Channel 。

 
内存导致的反压

当LocalBufferPool已申请的内存数量达到上限(或NetworkBufferPool没有可用内存块)时,
对于写入:Task 的 Netty Channel会暂停读取,上游的发送端会立即响应停止发送,拓扑会进入反压状态。
对于写出:Task线程将结果写到RP,RP也会向LocalBufferPool请求内存块,没有可用的内存块,则会堵塞到请求内存块的地方。

 

3.2. 内存块回收

当一个内存块被消费后,调用 Buffer.recycle() 方法,将内存块还给 LocalBufferPool 。
如果 LocalBufferPool 中当前申请的数量超过了池子容量,则LocalBufferPool 会将该内存块回收给 NetworkBufferPool。如果没超过池子容量,则会继续留在池子中,减少反复申请的开销。

【内存模型】Flink内存模型:从宏观(Flink内存模型)、微观(Flink内存结构)、数据传输等角度分析Flink的内存管理_第7张图片

 
 

参考:
https://zhuanlan.zhihu.com/p/427896176

你可能感兴趣的:(#,flink,实战,flink,java)