Spark与FLink的内存管理(一)

最近被Spark和Flink的内存管理搞吐了,所以专门整理一下🙂🙂

Spark的内存管理

Spark1.6版本之后引入的统一内存管理机制,与静态内存管理机制的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域

统一内存管理的堆内结构如图所示,

在这里插入图片描述

统一内存管理的堆外内存结构如下图所示:

在这里插入图片描述

其中最重要的优化在于动态占用机制,其规则如下:

  1. 设定基本的存储内存和执行内存区域(Spark.storage.storageFraction参数),该设定确定了双方各自拥有的空间的范围;
  2. 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的Block)
  3. 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后“归还”借用的空间;
  4. 存储内存的空间被对方占用后,无法让对方“归还”,因为需要考虑Shuffer过程中的很多因素,实现起来较为复杂。

统一内存管理的动态占用机制如图所示:

在这里插入图片描述

凭借统一内存管理机制,Spark在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护Spark内存的难度,但并不意味这开发者可以高枕无忧。如果存储内存的空间太大或者缓存的数据过多,反而导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的RDD数据通常都是长期驻留内存的。所以要想充分发挥Spark的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和原理。

  • 存储内存管理

    1. RDD的持久化机制

      RDD作为Spark最基本的数据抽象,只能基于在稳定物理存储中的数据集上创建,或者在其他已有的RDD上执行转换操作产生一个新的RDD。转换后的RDD与原始的RDD之间产生的依赖关系,构成了血统。凭借血统,Spark保证了每一个RDD都可以重新恢复。但RDD的所有转换都是惰性的,即只有当一个返回结果给给Driver的行动发生时,Spark才会创建任务读取RDD,然后真正触发转换的执行。

      Task在启动之初读取一个分区时,会先判断这个分区是否已经被持久化,如果没有则需检查Checkpoint或按照血统重新计算。所以如果一个RDD上要执行多次行动,可以在第一次行动中使用persist或cache方法,在内存或磁盘中持久化或缓存这个RDD,从而在后面的行动时提升计算速度。

      事实上,cache方法使用默认的MEMORY_ONLY的存储级别将RDD持久化到内存,故缓存是一种特殊的持久化。堆外和堆内存储内存的设计,便可以对缓存RDD时使用统一内存做统一的规划和管理。

      RDD的持久化由Spark的Storage模块负责,实现了RDD与物理存储的解耦合。Storage模块负责管理Spark在计算过程中产生的数据,将那些在内存或磁盘、在本地或远程存取数据的功能封装了起来。在具体实现时Driver端和Executor端的Storage模块构成了主从式的架构,即Driver端的BlockManager为Master,Executor端的BlockManager为Slave。Storage模块在逻辑上以Block为基本存储单位,RDD的每个Partition经过处理后唯一对应一个Block。Driver端的Master负责整个Spark应用程序的Block的元数据信息的管理和维护,而Executor端的Slave需要将Block的更新等状态上报到Master,同时接收Master的命令,例如新增或删除一个RDD。

      在这里插入图片描述

在对RDD持久化时,Spark规定了MEMORY_ONLY、MEMORY_AND_DISK等7种不同的存储级别,而存储级别是以下5个变量的组合:

class StorageLevel private(
private var _useDisk: Boolean, //磁盘
private var _useMemory: Boolean, //这里其实是指堆内内存
private var _useOffHeap: Boolean, //堆外内存
private var _deserialized: Boolean, //是否为非序列化
private var _replication: Int = 1 //副本个数
)

Spark种7种存储级别如下:

持久化级别含义
MEMORY_ONLY以非序列化的Java对象的方式持久化在JVM内存中。如果内存无法完全存储RDD所有的partition,那么那些没有持久化的partition就会在下一次需要使用它们的时候,重新被计算
MEMORY_AND_DISK同上,但是当某些partition无法存储在内存中时,会持久化到磁盘中。下次需要使用这些partition时,需要从磁盘上读取
MEMORY_ONLY_SER同MEMORY_ONLY,但是会使用Java序列化方式,将Java对象序列化后进行持久化。可以减少内存开销,但是需要进行反序列化,因此会加大CPU开销
MEMORY_AND_DISK_SER同MEMORY_AND_DISK,但是使用序列化方式持久化Java对象
DISK_ONLY使用非序列化Java对象的方式持久化,完全存储到磁盘上
MEMORY_ONLY_2MEMORY_AND_DISK_2等等如果是尾部加了2的持久化级别,表示将持久化数据复用一份,保存到其他节点,从而在数据丢失时,不需要再次计算,只需要使用备份数据即可

通过对数据结构的分析,可以看出存储级别从三个维度定义了RDD的Partition的存储方式:

  • 存储位置:磁盘、堆内内存、堆外内存。如MEMORY_AND_DISK是同时在磁盘和堆内内存上存储,实现了冗余备份。
  • 存储形式:Block缓存到存储内存后,是否为非序列化的形式。如MEMORY_ONLY是非序列化方式存储,OFF_HEAP是序列化方式存储。
  • 副本数量:大于1时需要远程冗余备份到其他节点。
  1. RDD的缓存过程

    RDD在缓存到内存存储之前,Partition中的数据一般以迭代器(Iterator)的数据结构来访问,这是Scala语言中一种遍历数据集合的方法。通过Iterator可以获取分区中每一条序列化或者非序列化的数据项(Record),这些Record的对象实例化在逻辑上占用了JVM堆内存的other部分的空间,同一Partition的不同Record的不同Record的存储空间并不连续、

    RDD在缓存到存储内存之后,Partition被转换为Block,Record在堆内存或堆外内存中占用一块连续的空间。将Partiton由不连续的存储空间转换为连续存储空间的过程,Spark称之为“展开”(Unroll)。

    Blcok有序列化和非序列化两种存储格式,具体以那种方式取决于该RDD的存储级别。非序列化的Block以一种DeseializedMemryEntry的数据结构来定义,用一个数组存储所有的对象实例,序列化的Block则以SerializedMemoryEntry的数据结构来定义,用字节缓冲区(ByteBuffer)来存储二进制数据。每个Executor的Storage模块用一个链式Map结构(LinedHashMap)来管理堆内存和堆外内存中所有的Block对象的实例,对这个LinkedHashMap新增和删除间接记录了内存中的申请和释放。

    因为不能保证存储空间可以一次容纳Iterator中的所有数据,当前的计算任务在Unroll时要向MemoryManager申请足够的Unroll空间来临时占位,空间不足则Unroll失败,空间足够时可以继续进行。

    对于序列化的Partition,其所需的Unroll空间可以直接累加计算,一次申请。对于非序列化的Partition则要在遍历Record的过程中一次申请,即每次读取一条Record,采样估算其所需的Unroll空间并进行申请,空间不足时可以中断,释放已占用的Unroll空间。

    如果最终的Unroll成功,当前Partititon所占用的Unroll空间被转换为正常的缓存RDD的存储空间,如下所示

    在这里插入图片描述

在静态内存管理时,Spark在存储内存中专门划分一块Unroll空间,其大小是固定的,同一内存管理时则没有对Unroll空间进行特别区分,当存储空间不足时会根据动态占用机制进行处理。

  1. 淘汰与落盘

    由于同一个Executor的所有计算任务共享有限的存储空间,当有薪的Block需要缓存但是剩余空间不足且无法动态占用时,就要对LinkedHashMap中的旧Block进行淘汰(Eviction),而淘汰的Block如果其存储级别中同时包含存储到磁盘的要求,则要对其进行落盘(Drop),否则直接删除该Block。

    存储内存的淘汰规则为:

    • 被淘汰的旧Block要与新Block的MemoryMode相同,即同属于堆外或堆内内存;
    • 新旧Block不能同属于一个RDD,避免循环淘汰;
    • 旧Block所属RDD不能处于被读状态,避免引发一致性问题;
    • 遍历LinkedHashMap中Block,按照最近最少使用(LRU)的顺序淘汰,直到满足新Block所需的空间。其中LRU时LinkedHashMap的特性。落盘的流程则比较简单,如果其存储级别符合useDISK为true的条件,再根据其deserialized判断是否是非序列化的形式,若是则对其进行序列化,最后将数据存储到磁盘,在Storage模块中更新其信息。
  • 执行内存管理

    执行内存主要是用来存储任务在执行Shuffer时占用的内存,Shuffle是按照一定规则对RDD数据重新分区的过程,我们来看Shuffle的Write个Read两阶段对执行内存的使用:

    1. Shuffle Write

      若在map端选择普通的排序方式,会采用ExternalSorter进行外排,在内存中存储数据时主要占用堆内执行空间。

      若在map端选择Tungsten的排序方式,则采用ShuffleExternalSorter直接对其序列化形式存储的数据排序,在内存中存储数据时可以占用堆外或堆内执行空间,取决于用户是否开启了堆外内存以及堆外执行内存是否足够。

    2. Shuffer Read

      在对reduce端的数据进行聚合时,要将数据交给Aggregator处理,在内存中存储数据时占用堆内执行空间。如果需要进行最终结果排序,则要将再次将数据交给ExternalSorter处理,占用堆内执行空间。

      在ExternalSorter和Aggregator中,Spark会使用一种叫AppendOnlyMap的哈希表在堆内执行内存中存储数据,但是Shuffer过程中所有数据并不能都保存到该哈希表中,当这个哈希表占用的内存会进行周期性地采样估算,当其大到一定程度,无法在从MemoryManager申请到新的新型内存时,Spark就会将全部内容存储到磁盘文件中,这个过程称之为溢存(Spill),溢存到磁盘的文件最后会被归并(Merge)。

      Shuffer Write阶段中用到的Tungsten时Databricks公司提出的对Spark优化内存和CPU使用的计划(钨丝计划),解决了一些JVM在性能上的限制和弊端。Spark会根据Shuffer的情况来自动选择是否采用Tungsten排序。

    Spark的存储内存和执行内存有着截然不同的管理方式:对于存储内存来说,Spark用一个LinkedHashMap来集中管理所有的Block,Block由需要缓存的RDD的partition转化而成;而对于执行内存,Spark用AppendOnlyMap来存储Shuffer过程中的数据,在Tungsten排序中甚至抽象成为页式内存管理,开辟了全新的JVM内存管理机制。

k,Block由需要缓存的RDD的partition转化而成;而对于执行内存,Spark用AppendOnlyMap来存储Shuffer过程中的数据,在Tungsten排序中甚至抽象成为页式内存管理,开辟了全新的JVM内存管理机制。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: SparkFlink都是目前非常流行的大数据处理框架。 Spark是由Apache软件基金会开发的开源分布式计算系统,旨在提供快速的大规模数据处理和分析能力。Spark的核心组件是Spark Core,它提供了分布式任务调度、内存计算和数据处理能力。除了Spark Core之外,Spark还提供了Spark SQL、Spark Streaming、MLlib和GraphX等组件,可以支持各种不同的数据处理和分析任务。 Flink是由Apache软件基金会开发的开源流处理框架,它旨在提供实时、分布式的数据处理能力。Flink使用了一种称为“流计算”的技术,可以对数据进行流式处理,同时支持批处理和迭代计算。Flink还提供了Flink SQL、Flink Streaming和Flink ML等组件,可以支持各种不同类型的数据处理任务。 尽管SparkFlink都是大数据处理框架,它们的设计思想和实现方式有所不同,因此在不同的应用场景下可能会有不同的优缺点。例如,Spark在批处理方面表现优异,而Flink在流处理方面表现更为出色。 ### 回答2: SparkFlink是两个非常流行的实时大数据处理框架。它们都可以用于分布式计算和处理大规模数据,但在某些方面却有所不同。 Spark是一个内存计算框架,它使用弹性分布式数据集(RDD)来进行计算。RDD是一个可并行操作的数据集合,它会被分割成多个分区并分布在不同的计算节点上。Spark提供了丰富的API和内置的库来支持各种大数据应用,如批处理、交互式查询和流式处理。Spark具有高效的执行引擎,可以在内存中快速处理数据,并通过缓存和数据共享来提高计算性能。此外,Spark还提供了Spark Streaming,使其能够处理实时数据流。 而Flink是一个分布式流处理框架,它提供了低延迟、高吞吐量的流式计算功能。Flink使用流作为基本的数据处理单位,可以对数据流进行连续计算和转换。它支持事件时间和处理时间两种不同的时间概念,并提供了窗口操作和状态管理功能来处理有状态的计算。Flink还具备容错机制,可以自动恢复错误和故障,并支持精确一次的状态一致性。此外,Flink还可以将批处理和流处理结合在一起,实现一体化的数据处理。 总之,SparkFlink都是优秀的大数据处理框架,它们在内存计算、数据处理模型和特性方面略有不同。选择使用哪个框架取决于具体的应用场景和需求。如果处理的是批量数据且需要实时性较高,可以考虑使用Spark;而如果处理的是连续的实时数据流且对低延迟有较高要求,可以选择Flink。 ### 回答3: SparkFlink是两个流行的大数据处理框架。Spark是一个基于内存的分布式计算框架,它提供了高级API用于批处理和流式处理。Spark的核心组件是Spark Core,它负责任务调度、数据分发和内存管理。除了Spark Core,Spark还提供了Spark SQL用于结构化数据处理、Spark Streaming用于实时数据处理、MLlib用于机器学习和GraphX用于图形计算。Spark能够通过将数据存储在内存中来提高计算效率,从而加快处理速度。 相比之下,Flink是一个可扩展的流式处理引擎,它支持大规模的数据处理和分析。Flink提供了流式处理和批处理的统一API,这意味着它可以同时处理实时数据和历史数据。Flink具有一个称为DataStream的高级API,用于处理无界数据流,以及一个称为DataSet的API,用于批处理任务。 Flink的核心组件是分布式数据流引擎,它提供了支持容错、高吞吐量和低延迟的数据处理。Flink还提供了用于状态管理的机制,以及用于处理时间和窗口的工具。 尽管SparkFlink都是大数据处理框架,但它们在设计和用途上有一些区别。Spark更适合于交互式分析、机器学习和图计算等场景,而Flink则更适合于需要低延迟和准确性的实时数据处理任务。另外,Flink在容错性和事件时间处理方面做得更好,而Spark则更擅长于内存计算。选择使用哪个框架取决于数据处理的需求和场景,以及对延迟、内存管理和容错性的偏好。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值