Flink系统知识讲解之:Flink内存管理详解

Flink系统知识讲解之:Flink内存管理详解

在现阶段,大部分开源的大数据计算引擎都是用Java或者是基于JVM的编程语言实现的,如Apache Hadoop、Apache Spark、Apache Drill、Apache Flink等。Java语言的好处是不用考虑底层,降低了程序员的门槛,JVM可以对代码进行深度优化,对内存资源进行管理,自动回收内存。但是自动内存管理的问题在于不可控,基于JVM的大数据引擎常常会面临一个问题,即在处理海量数据时,如何在内存中存储大量的数据。

自主内存管理

Flink从一开始就选择了自主的内存管理,避开了JVM内存管理在大数据场景下的问题,提升了计算效率。

1.JVM内存管理的不足

当需要将海量数据存储到内存中时,就不得不面对JVM存在的几个问题:
(1)有效数据密度低
Java的对象在内存中的存储包含3个主要部分:对象头、实例数据、对齐填充部分。32位和64位的虚拟机中对象头分别需要占32bit和64bit。实例数据时实际的数据存储。为了提高效率,内存中数据存储不是连续的,而是按照8 byte的整数倍进行存储。例如,只有一个boolean字段的类实例占16 byte:头信息8 byte,boolean 1 byte,为了对齐达到8的倍数会额外占用7 byte。这就导致在JVM中有效信息的存储密度很低。

(2)垃圾回收
JVM的内存回收机制的优点和缺点同样明显,优点是开发者无需关注资源回收问题,可以提高开发效率,减少内存泄漏的可能。但是内存回收是不可控的,在大数据计算的场景中,这个缺点被放大,TB、PB级的数据计算需要消耗大量的内存,在内存中产生海量的Java对象。一旦出现Full GC,GC会达到秒级甚至分钟级,直接影响执行效率。
GC带来的中断会使集群中的心跳超时,导致节点被踢出集群,整个集群进入不稳定状态。虽然通过JVM参数调优可以提升回收效率,尽量减少Full GC的发生,但是仍然不能避免这个问题,精确的调优也确实非常困难。

(3)OOM问题影响稳定性
OutOfMemoryError是分布式计算框架经常会遇到的问题,当JVM中所有对象大小超过分配给JVM的内存大小时,就会发生OutOfMemoryError错误,导致JVM崩溃,分布式框架的健壮性和性能都会受到影响。

(4)缓存未命中问题
CPU进行计算的时候,是从CPU缓存中获取数据,而不是直接从内存中获取数据。 CPU有分L1、L2、L3级缓存。L1小,一般为32KB,L3大,能达到32MB。缓存的理论基础是程序局部性原理,包括时间局部性和空间局部性:最近被CPU访问的数据,短期内CPU还要访问(时间);被CPU访问的数据附近的数据,CPU短期内还要访问(空间)。Java对象在堆上存储的时候并不是连续的,所以从内存中读取Java对象时,缓存的邻近的内存区域的数据往往不是CPU下一步计算所需要的,这就是缓冲未命中。此时CPU需要空转等待从内存中重新读取数据。CPU的速度和内存的速度之间差好几个数量级,导致CPU没有充分利用起来。如果数据没有在内存中,而是需要从磁盘上加载,那么执行效率就会变得惨不忍睹。

2.自主内存管理

因为直接使用JVM做内存管理在大数据场景下可能遇到的诸多问题,所以越来越多的大数据计算引擎选择自行管理JVM内存,如Spark、Flink、HBase,尽量达到C/C++一样的性能,同时避免OOM的发生。本章主要介绍Flink是如何解决上面的问题的,主要内容包括内存管理、定制的序列化工具、缓存友好的数据结构和算法、堆外内存等。

在Flink中,Java对象的有效信息被序列化为二进制数据流,在内存中连续存储,保存在预分配的内存块上,内存块叫做MemorySegment。MemorySegment是内存分配的最小单元,是一段固定长度的内存(默认大小为32KB)。同时,Flink为其提供了非常高效的读写方法,很多运算可以直接操作二进制数据,而不需要反序列化即可执行。

MemorySegment可以保存在堆上,其内部存储为一个Java byte数组,也可以保存在堆外的ByteBuffer中。每条记录都会以序列化的形式存储在一个或多个MemorySegment中。

但使用堆上内存,仍然不是完全自主的内存管理,还存在以下问题:
1)超大内存(上百GB)JVM的启动需要很长时间,Full GC可以达到分钟级。使用堆外内存,可以将大量的数据保存在堆外,极大地减小堆内存,避免GC和内存溢出的问题。
2)高效的IO操作。堆外内存在写磁盘或网络传输时采用的是零拷贝,而堆上内存则至少需要1次内存复制。

3.堆外内存的不足之处

堆外内存提供了更好的性能和更可控的内存管理,但是也存在几个问题:
1)堆上内存的使用、监控、调试简单,堆外内存出现问题后的诊断则较为复杂。
2)Flink有时需要分配短生命周期的MemorySegment,在堆外内存上分配比在堆上内存开销更高。
3)在Flink的测试中,部分操作在堆外内存上会比堆上内存慢。

同时为了提供效率,Flink在计算中采用了DBMS的Sort和Join算法,直接操作二进制数据,避免数据反复序列化带来的开销。Flink的内部实现更像C/C++而非Java。

内存模型

内存布局

TaskManager是Flink中执行计算的核心组件,是用来运行用户代码的Java进程。其中,大量使用了堆外内存。
Flink TaskManager的简化和详细内存结构如下图所示:
简化内存模型:
在这里插入图片描述

详细内存模型:
在这里插入图片描述

基于文初提及的使用JVM堆上内存的一些不足之处,Flink设计了使用堆外内存的自主内存管理。因此,Flink任务进程的总内存(Total Process Memory, TPM)= Flink自身使用的内存(Total Flink Memory, TFM) + JVM运行额外的内存(如Metaspace、overhead)。其中,Flink自身使用的内存(TFM)包括了JVM堆内存和自主管理的堆外内存,堆外内存又包含了托管内存和直接内存。下面分别对这些分类进行介绍:

JVM Heap

Framework Heap

这部分内存主要由Flink框架自身使用,用于存储系统级别的数据结构,包括Flink框架在运行期间需要的一些数据结构,例如任务的线程栈内存和其他Flink框架的基础设施。例如用于JobManager和TaskManager的RPC消息、管理检查点的元数据等。它是作业执行所必需的基本内存,独立于用户程序和运行期间的数据存储。

Task Heap

这部分内存主要用于存储由用户函数创建的Java对象和用户函数操作的数据。例如,当执行一个map操作,您的函数可能会创建一些新的Java对象,这些对象都是在JVM堆内存中创建和管理的。如果Flink的托管内存配置为堆内,那么Flink的排序、哈希和状态后端操作也会使用到Task Heap内存。

Off-Heap

托管内存(Managed Memory)

托管内存是由 Flink 负责分配和管理的本地(堆外)内存。 以下场景需要使用托管内存:

  • 流处理作业中用于 RocksDB State Backend
  • 流处理和批处理作业中用于排序、哈希表及缓存中间结果。
  • 流处理和批处理作业中用于在 Python 进程中执行用户自定义函数
    更具体的,对于当作业中使用排序、哈希表及缓存中间结果时,Flink是如何使用托管内存的:
  • 排序:例如,当你需要对一个非常大的数据集进行排序时,如果数据无法完全装入内存,Flink 就会使用其托管内存来执行外排序。在外排序过程中,数据会被分割成可以装入内存的小块,每个小块内部进行排序,然后将排序后的小块写入磁盘。当所有小块都进行了排序和写入后,Flink 会从磁盘读取这些小块,执行归并排序,直到所有数据都被排序。
  • 哈希表:Flink 在处理连接(Join)操作时,经常需要使用哈希表来维护到目前为止已经看到的数据记录。如果不能将所有数据装入内存,Flink 就会使用托管内存来存储这个哈希表。这样就可以保证即使在处理大规模数据时也能保持良好的性能。
  • 缓存中间结果:在一些需要多遍扫描数据的算法(比如迭代算法)中,Flink 会缓存数据的中间结果,以便下一轮迭代可以重复使用,这样可以减少数据重复读取的开销。Flink 托管内存就是用来存储这些中间结果的。
    另外,当使用 RocksDB 作为状态后端时,Flink 托管内存主要被用作 RocksDB 的写缓冲区(Write Buffer)和读缓存(Block Cache),从而提高状态访问的速度。
    简言之,Flink 的托管内存主要用于存储在处理过程中需要存储的中间计算数据和结果,以求在充分利用有限内存资源的同时提供尽可能高的处理速度。
直接内存()

直接内存通常指的是被Flink进程直接从操作系统中申请的、不受Java堆内存垃圾回收器管理的内存。 以下场景需要使用直接内存:

  • 于网络通信和文件I/O,
  • 通过网络缓冲池进行数据交换(如shuffle)
  • 数据缓冲以及序列化/反序列化中进行应用。
    Flink通过直接内存技术进行数据交换可以有效避免频繁的Java堆内存和本地I/O缓存之间的数据复制(利用零拷贝技术),从而提高性能。
    在一些情况下,直接内存也可以利用在某些需要大量内存并希望避免频繁触发垃圾回收的处理中,例如当使用RocksDB作为状态后端时,RocksDB的本地内存通常是由直接内存提供的,这样可以避免状态数据引起的Java堆内存的显著增加,从而降低了垃圾收集的开销和提高了性能。

直接内存包括了以下几部分:

  • Framework Off-Heap:这部分内存被Flink框架用于框架自身的一些运行需求。比如,Flink的一些本地数据结构和算法可能会使用这部分内存进行操作。这部分内存一般不大。
  • Task Off-Heap:这部分内存主要用于存储由用户任务产生的、并由Flink以某种形式管理的内存。比如,如果你配置了本地
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JermeryBesian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值