内存管理
Spark和Flink都是由Java语言开发的,与C++不同,Java语言本身提供了垃圾回收机制来进行内存管理。如果说这种垃圾回收机制非常快,机器内存也足够多,那么Spark和Flink也无需再进行内存管理了。显然,这种假设是不合理的。
我们可以从以下两个思路来进行内存管理的优化:
- 默认垃圾回收GC的设置不一定是高效的,因此通过调整JVM的垃圾回收GC参数,使得GC能够快速地进行内存回收,这种方式并不改变系统本身的源码。
- 在处理大量数据时会生成大量对象,机器内存往往是不够的,Java GC可能会被反复触发,其中Full GC或Major GC的开销是非常大的。因此通过显式地进行内存申请与回收,避免频繁地调用GC,尤其是Full GC或Major GC,这种方式需要系统源码加入内存管理模块。
两种方式的目的都是在给定的内存中,减小Java垃圾回收机制对系统的影响。
调节GC的参数
第一种方式依赖于大量的实验及对gc日志的分析,可以参考这里。
插一句,堆与栈的概念。简单来讲,栈中存放的是一些局部变量,堆中是new出来的内容。栈由系统直接分配,而堆可通过用户程序申请。用户程序和Java的垃圾回收都无法控制栈,而只是控制堆。
垃圾回收机制可参见这里,简要总结如下表。
| Eden区 | Survivor区 | Tenured/Old区 | |
|---|---|---|---|
| Minor GC | √ | √ | × |
| Major GC | × | × | √ |
| Full GC | √ | √ | √ |
显式内存管理
第二种方式主要思想是将JVM的heap进行划分成负责执行的内存块、负责缓存的内存块以及其它部分,各个部分都限定在一定的范围内,如果超过该范围则将一部分内容溢写到磁盘。 在磁盘溢写时涉及到对象的序列化与反序列化,当然在网络传输时也需要考虑这个。此外,Spark和Flink也都支持利用off-heap,即JVM以外的内存,这点非常像C++。
对Heap的划分
Spark和Flink在对heap划分时使用的名称不太一样,但功能基本一致。
| 负责执行的内存块 | 负责缓存的内存块 | 其它内存块 | |
|---|---|---|---|
| Spark | Execution Memory | Storage Memory | other |
| Flink | Memory Manager | Network Buffer Pool | Remaining Heap |
Flink中Memory Manager和Network Buffer Pool申请释放内存的单位都是固定大小的Memory Segment。 Spark中Storage Memory以Block为单位管理内存,Execution Memory以MemoryPool为单位管理?
- 负责执行的内存块:该内存区域用于Task执行各种操作,包括join、sort和aggregation以及shuffle;
- 负责缓存的内存块:该内存区域用于Task之间数据的传输;
- 其它内存块:用户代码中的一些临时对象等。
其中,Spark中Storage主要用于缓存RDD Partitions,以及将容量大的任务结果传播和发送给driver;而Flink中Network Buffers主要用于数据的网络传输。只不过SparK是等待下游Task来取数据,而Flink是主动向下游发送数据。
优点:
- 数据的连续存储,有利于提高L1/L2/L3缓存的命中率。
- 溢写数据到磁盘可以保证内存消耗在可控范围内,减小了Full GC或Major GC触发的可能性。
缺点:
- 按比例划分内存块,可能与实际使用情况不匹配,造成资源浪费。
Spark 1.6新特性:支持Execution和Storage内存的相互借用。
Off-heap的使用
都支持off-heap的使用......
序列化与反序列化
简单地说,序列化与反序列化可以理解为编码与解码的过程。序列化以后的数据希望占用比较小的空间,而且数据能够被正确地反序列化出来。为了能正确反序列化,序列化时仅存储二进制数据本身肯定不够,需要增加一些辅助的描述信息。此处可以采用不同的策略,因而产生了很多不同的序列化方法。Java本身自带的序列化和反序列化的功能,但是辅助信息占用空间比较大,在序列化对象时记录了过多的类信息。
在磁盘IO和网络传输时会用到序列化与反序列化。此外,在内存中表达时,序列化也可以做到节约空间。这是因为Java对象比原生数据大很多,见这里。
- Spark提供了两种序列化方式:Java序列化和Kryo序列化。
- Flink自己实现了一套序列化机制,也会借用Kryo的功能。
在这一点上,Flink能更加积极地对内存进行有效控制。在内存中,Spark和Flink均支持将数据序列化存储直接对二进制数据的操作(否则,需要进行反序列化)。