Flink内存管理: 网络传输内存network buffer及堆内存管理

1 Flink背压原理

任务A写,  任务B读, 2者都是先申请 local buffer pool, 满了之后再向network buffer pool申请。  

消费下游:   local buffer pool和network buffer pool满了之后,发送消息给上游(ResultSubparittion) ,上有不在发送消息,下游的  input channel将不会接受到新的数据

发送上游:ResultSubparittion也会将local buffer pool和network buffer pool填满,消息写将不再产生消息。同样的他的上游将阻塞。直到source,消费数据变慢。

具体逻辑可以参看http://www.54tianzhisheng.cn/2019/08/26/flink-back-pressure/

2 Network buffer pool

从背压原理,我们可以看到2个重要的缓存localbuffer和network buffer。

   其中network buffer是任务管理器下所有子任务共享。   而local buffer为单独的子任务所有。实际上local buffer就是network buffer,只是逻辑上大小分配受到相关限制。

参数1:taskmanager.memory.segment-size(默认为32K)

Network Buffer Pool主要用于数据的网络传输。在 TaskManager 启动的时候就会分配。默认数量是 2048 个

Flink 在数据传输时,会把数据序列化成二进制然后写到 Buffer 中,当 Buffer 满了,需要 Flush(默认为32KiB,通过设置)。

默认值基本够用,无需调优。

参数2:env.setBufferTimeout(timeoutMillis) 

可能1分钟都没有 32 KB的数据,就会导致1分钟内的数据都积攒在 Buffer 中不会发送到下游 Task 去处理,从而导致数据出现延迟,这并不是我们想看到的。所以 Flink 有一个 Buffer timeout 的策略,意思是当数据量比较少,Buffer 一直没有变满时,后台的 Output flusher 线程会强制地将 Buffer 中的数据 Flush 到下游。Flink 中默认 timeout 时间是 100ms,即:Buffer 中的数据要么变满时 Flush,要么最多等 100ms 也会 Flush 来保证数据不会出现很大的延迟。当然这个可以通过 env.setBufferTimeout(timeoutMillis) 来控制超时时间。

  • timeoutMillis > 0 表示最长等待 timeoutMillis 时间,就会flush
  • timeoutMillis = 0 表示每条数据都会触发 flush,直接将数据发送到下游,相当于没有Buffer了(避免设置为0,可能导致性能下降)
  • timeoutMillis = -1 表示只有等到 buffer满了或 CheckPoint的时候,才会flush。相当于取消了 timeout 策略

一些特殊的消息如果通过 RecordWriter 发送,也会触发立即 Flush 缓存的数据。其中最重要的消息包括 Checkpoint barrier 以及 end-of-partition 事件

Output flusher 不提供任何保证——它只向 Netty 发送通知,而 Netty 线程会按照能力与意愿进行处理。这也意味着如果存在反压,则 Output flusher 是无效的。言外之意,如果反压很严重,下游 Buffer 都满了,当然不能强制一直往下游发数据。

注: Network pool初始化内存段,使用堆外内存初始化:

参看1.9代码NetworkbBufferPool.java 119行,       默认32K*2048=64M
availableMemorySegments.add(MemorySegmentFactory.allocateUnpooledOffHeapMemory(segmentSize, null));

3 堆内存管理

Remaining (Free) Heap:这部分的内存是留给用户代码以及 TaskManager 的数据结构使用的。

Memory Manager Pool:

由 MemoryManager 管理的,由众多MemorySegment组成的超大集合。Flink 中的算法(如 sort/shuffle/join)会向这个内存池申请 MemorySegment,将序列化后的数据存于其中,使用完后释放回内存池。默认情况下,池子占了堆内存的 70% 的大小。


注:Memory Manager Pool 主要在Batch模式下使用。在Steaming模式下,该池子不会预分配内存,也不会向该池子请求内存块。也就是说该部分的内存都是可以给用户代码使用的。社区打算在 Streaming 模式下也能将该池子利用起来。

  • 减少GC压力。显而易见,因为所有常驻型数据都以二进制的形式存在 Flink 的MemoryManager中,这些MemorySegment一直呆在老年代而不会被GC回收。其他的数据对象基本上是由用户代码生成的短生命周期对象,这部分对象可以被 Minor GC 快速回收。只要用户不去创建大量类似缓存的常驻型对象,那么老年代的大小是不会变的,Major GC也就永远不会发生。从而有效地降低了垃圾回收的压力。另外,这里的内存块还可以是堆外内存,这可以使得 JVM 内存更小,从而加速垃圾回收。

  • 避免了OOM。所有的运行时数据结构和算法只能通过内存池申请内存,保证了其使用的内存大小是固定的,不会因为运行时数据结构和算法而发生OOM。在内存吃紧的情况下,算法(sort/join等)会高效地将一大批内存块写到磁盘,之后再读回来。因此,OutOfMemoryErrors可以有效地被避免。

  • 节省内存空间。Java 对象在存储上有很多额外的消耗。如果只存储实际数据的二进制内容,就可以避免这部分消耗。

  • 高效的二进制操作 & 缓存友好的计算。二进制数据以定义好的格式存储,可以高效地比较与操作。另外,该二进制形式可以把相关的值,以及hash值,键值和指针等相邻地放进内存中。这使得数据结构可以对高速缓存更友好,可以从 L1/L2/L3 缓存获得性能的提升

常用参数配置:https://ci.apache.org/projects/flink/flink-docs-release-1.9/ops/config.html

Key Default Description

taskmanager.memory.fraction

0.7

任务管理器内存用于内部数据缓存占用整个堆(堆内或堆外,依赖taskmanager.memory.off-heap参数)的比例,taskmanager.memory.size未配置时有效。

taskmanager.memory.off-heap

false

 内存分配方式(JVM heap or off-heap), used for managed memory of the TaskManager.

当需要非常大量内存时,这个参数为true可以提及高内存操作性能。
 

taskmanager.memory.preallocate

false

TaskManager 启动时managed memory预分配内存. taskmanager.memory.off-heap 为true, 建议本值为 true.

如果设置为false, 会导致JVM 仅当MaxDirectMemorySize 达到限定值才会触发 full GC. 对于流式处理,推荐设置为false,因the core state backends currently do not use the managed memory.

疑问:  流式窗体是否算批处理呢?

taskmanager.memory.segment-size

"32kb" Size of memory buffers used by the network stack and the memory manager.

taskmanager.memory.size

"0"  task manager 堆内或堆外size(依赖参数taskmanager.memory.off-heap) for sorting, hash tables, and caching of intermediate results. 未指定,则以参数为准 taskmanager.memory.fraction.

4 Memory Manager初始化过程源码:

    private static MemoryManager createMemoryManager(
            TaskManagerServicesConfiguration taskManagerServicesConfiguration) throws Exception {
        // computing the amount of memory to use depends on how much memory is available
        // it strictly needs to happen AFTER the network stack has been initialized

        // check if a value has been configured
        // 参数taskmanager.memory.size对应的值
        long configuredMemory = taskManagerServicesConfiguration.getConfiguredMemory();

        MemoryType memType = taskManagerServicesConfiguration.getMemoryType();

        final long memorySize;

        boolean preAllocateMemory = taskManagerServicesConfiguration.isPreAllocateMemory();

        // 参数taskmanager.memory.size已经配置
        if (configuredMemory > 0) {
            //参数 taskmanager.memory.preallocate
            if (preAllocateMemory) {
                LOG.info("Using {} MB for managed memory." , configuredMemory);
            } else {
                LOG.info("Limiting managed memory to {} MB, memory will be allocated lazily." , configuredMemory);
            }
            memorySize = configuredMemory << 20; // megabytes to bytes
        } else {// 没有配置taskmanager内存的情况
            // similar to #calculateNetworkBufferMemory(TaskManagerServicesConfiguration tmConfig)
            // 参数taskmanager.memory.fraction
            float memoryFraction = taskManagerServicesConfiguration.getMemoryFraction();

            // 在堆内
            if (memType == MemoryType.HEAP) {
            // taskManagerServicesConfiguration在类 TaskManagerRunner 355行实例化、初始化
            // FreeHeapMemoryWithDefrag来自于360行 EnvironmentInformation.getSizeOfFreeHeapMemoryWithDefrag()
            // EnvironmentInformation的Line150:   getSizeOfFreeHeapMemoryWithDefrag() = getMaxJvmHeapMemory()[设置值或物理内存1/4] - r.totalMemory()[当前虚拟机使用总内存] + r.freeMemory()[当前虚拟机空余内存] 
                long freeHeapMemoryWithDefrag = taskManagerServicesConfiguration.getFreeHeapMemoryWithDefrag();
                // network buffers allocated off-heap -> use memoryFraction of the available heap:
                long relativeMemSize = (long) (freeHeapMemoryWithDefrag * memoryFraction);
                if (preAllocateMemory) {
                    LOG.info("Using {} of the currently free heap space for managed heap memory ({} MB)." ,
                        memoryFraction , relativeMemSize >> 20);
                } else {
                    LOG.info("Limiting managed memory to {} of the currently free heap space ({} MB), " +
                        "memory will be allocated lazily." , memoryFraction , relativeMemSize >> 20);
                }
                memorySize = relativeMemSize;
            } 
            // 堆外
            else if (memType == MemoryType.OFF_HEAP) {
                long maxJvmHeapMemory = taskManagerServicesConfiguration.getMaxJvmHeapMemory();//[设置值或物理内存1/4] 
                // The maximum heap memory has been adjusted according to the fraction (see
                // calculateHeapSizeMB(long totalJavaMemorySizeMB, Configuration config)), i.e.
                // maxJvmHeap = jvmTotalNoNet - jvmTotalNoNet * memoryFraction = jvmTotalNoNet * (1 - memoryFraction)// 排除Network内存之后的内存*(1-F)=最大JVM堆内存
                // directMemorySize = jvmTotalNoNet * memoryFraction
                long directMemorySize = (long) (maxJvmHeapMemory / (1.0 - memoryFraction) * memoryFraction);
                if (preAllocateMemory) {
                    LOG.info("Using {} of the maximum memory size for managed off-heap memory ({} MB)." ,
                        memoryFraction, directMemorySize >> 20);
                } else {
                    LOG.info("Limiting managed memory to {} of the maximum memory size ({} MB)," +
                        " memory will be allocated lazily.", memoryFraction, directMemorySize >> 20);
                }
                memorySize = directMemorySize;
            } else {
                throw new RuntimeException("No supported memory type detected.");
            }
        }

        // now start the memory manager
        final MemoryManager memoryManager;
        try {
        //初始化 memory manager,根据堆内或对外,初始化内存池,按段存储段
            memoryManager = new MemoryManager(
                memorySize,
                taskManagerServicesConfiguration.getNumberOfSlots(),
                taskManagerServicesConfiguration.getPageSize(),
                memType,
                preAllocateMemory);
        } catch (OutOfMemoryError e) {
            if (memType == MemoryType.HEAP) {
                throw new Exception("OutOfMemory error (" + e.getMessage() +
                    ") while allocating the TaskManager heap memory (" + memorySize + " bytes).", e);
            } else if (memType == MemoryType.OFF_HEAP) {
                throw new Exception("OutOfMemory error (" + e.getMessage() +
                    ") while allocating the TaskManager off-heap memory (" + memorySize +
                    " bytes).Try increasing the maximum direct memory (-XX:MaxDirectMemorySize)", e);
            } else {
                throw e;
            }
        }
        return memoryManager;
    }
   
   
==》堆内

HybridHeapMemoryPool(int numInitialSegments, int segmentSize) {
   this.availableMemory = new ArrayDeque<>(numInitialSegments);
   this.segmentSize = segmentSize;

   for (int i = 0; i < numInitialSegments; i++) {
      this.availableMemory.add(new byte[segmentSize]);
   }
}

==》堆外

static final class HybridOffHeapMemoryPool extends MemoryPool {

   /** The collection of available memory segments. */
   private final ArrayDeque<ByteBuffer> availableMemory;

   private final int segmentSize;

   HybridOffHeapMemoryPool(int numInitialSegments, int segmentSize) {
      this.availableMemory = new ArrayDeque<>(numInitialSegments);
      this.segmentSize = segmentSize;

      for (int i = 0; i < numInitialSegments; i++) {
         this.availableMemory.add(ByteBuffer.allocateDirect(segmentSize));
      }
   }

参考:https://www.jianshu.com/p/68fad4f50d29

发布了16 篇原创文章 · 获赞 0 · 访问量 2841

猜你喜欢

转载自blog.csdn.net/peidezhi/article/details/103129863
今日推荐