作者回复: 先来说说这个spark.executor.memoryOverhead 哈,在yarn、k8s部署模式下,container会预留一部分内存,形式是堆外,用来保证稳定性,主要存储nio buffer,函数栈等一些开销,所以你看名字:over head。这部分内存,不管堆外还是堆内,开发者用不到,spark也用不到,所以不用关心,千万不指望调这个参数去提升性能,它的目的是保持运行时的稳定性~ 想利用堆外让spark去管理数据、加速执行效率,只有off heap那两个参数,一个用来enable(spark.memory.offHeap.enabled=true)、一个指定大小(spark.memory.offHeap.size)。这两个才是正儿八经的off heap key configs。 回答你的问题: 1. 是的,没错,它不是Spark管理的内存空间,不会用到Execution或是Storage 2. 不会,这部分overhead是单独划分的,它不会参与到Spark诸多内存空间的计算,是完全独立的一块区域,也就是前面说的container预留的“buffer”。所以完全不用理它,它的目的是提供稳定性,不参与Spark任务计算。
作者回复: 好问题,其实spark官方建议谨慎使用堆外内存,为啥呢? 原因其实很简单,在于堆外堆内的空间互不share,也就是说,你的task最开始用堆外,用着用着发现不够了,这个时候即使堆内还有空闲,task也没法用,所以照样会oom。 内存本来就有限,再强行划分出两块隔离的区域,其实反而增加了管理难度。tungsten在堆内其实也用内存页管理内存(Tungsten的相关优化,可以参考后面Tungsten那一讲),也用压缩的二进制数据结构,因此gc效率往往可以保障,这也是为什么官方推荐就用堆内就可以了。 回答你的问题,我不觉得有什么场景一定要用堆外,就我看来,对于开发者来说,堆外更多地是一种备选项,是Optional的。不过,尽管如此,我们还是要知道堆外、堆内各自有哪些优缺点、优劣势,这样在结合应用场景做选择的时候,也能有的放矢~
作者回复: 满分💯
作者回复: 具体大小可以通过参数来配置哈,堆内也一样,都是用参数开调控。不过需要注意,堆内、堆外的内存,互相之间不共享。也就是一开始你的task用off heap,后来用着用着发现不够了,这个时候是不能去占有堆内内存的,所以即便堆内有空闲,也还是会oom。所以在划分堆内堆外之前,要提前计划好,如果怕麻烦,就都用堆内。tungsten对于堆内的内存管理做的也很好,大多数场景都问题不大~
作者回复: 1. Tungsten确实统一了内存管理,使用Page来管理内存,这样做得目的,主要在于统一内存对象(内存页)抽象。对于堆内来说,内存页本质上就是个大对象,没什么新鲜的;但对于堆外来说,那可正儿八经的是OS的内存寻址。因此,两块内存不能“同时”使用。换句话说,一个任务,不管是执行任务、还是缓存任务,你要么用堆外,要么用堆内,驴和熊猫不可兼得,不能脚踏两条船。 2. 开启堆外之后,执行任务默认会走堆外,堆外用尽了,后续的任务才会走堆内。对于缓存来说,如果你明确指定了用off heap,那就是明确走堆外,如果你不明确指定,那么默认走堆内。
作者回复: “reduceByKey算子会引入 Shuffle,而 Shuffle 过程中所涉及的内部数据结构,如映射、排序、聚合等操作所仰仗的 Buffer、Array 和 HashMap,都会消耗 Execution Memory 区域中的内存。” 内存:上面说的这些操作,都会消耗内存空间,不过Map阶段的每一个计算环节,都是为了生成中间文件(data和index文件); 磁盘:在生成中间文件的时候,就会涉及磁盘、涉及diskStore的putByes写文件。比如临时文件溢出、比如merge得到的中间文件,等等。 或者更简单地,Shuffle过程中,只有写临时文件、和Shuffle中间文件,才会涉及diskStore和相关的磁盘操作。其他的计算步骤,都是在内存中完成的,会消耗如上的数据结构。
编辑回复: 1、spark中内存划分是逻辑上的,真正的管理还是在jvm。如user memory占用内存超过设定值,还是会占用框架内存。但框架内存会根据设定值让task做一些阻塞或spill操作,所以从这个层面上说,框架内存的值得正确设置,如用户不会用到大的list、map等内存集合,就要把用户内存空间设置得够小,以保证框架内存(执行内存+存储内存)足够大,避免不必要的阻塞或spill操作? 回答:是的,Spark的内存管理,更多的是一种”审计“上的管理,底下有JVM,Spark就不可能直接管理内存。通过内存管理机制,Spark更多地是设置一些软限制,从而从应用层面来将内存划分为不同区域,这些区域,在JVM看来,是没有区别的。如你所说:”如用户不会用到大的list、map等内存集合,就要把用户内存空间设置得够小,以保证框架内存(执行内存+存储内存)足够大,避免不必要的阻塞或spill操作“,确实是这样的。 2、如果开启了堆外内存,即使堆外内存不够,堆内内存充足,task也只会用堆外内存而不会用堆内内存? 回答:内存是用堆外,还是堆内,是以Job为粒度的,也就是说,要设置堆外内存,我们得确保堆外大小足以应对当前的作业,作业里面所有的tasks,都只能用堆外(如果作业在内存设置上用了堆外)。那么显然,此时跑在堆外的Job,假设内存不够用了,即便堆内还有剩余,也不会给这个Job用,这个Job还是会抛OOM。 3、spark 2.x版本中如果开启了堆外内存,并设置了spark.memory.offHeap.size=500mb,在yarn上跑的话spark.executor.memoryOverhead除了默认需要的10%是否还有要加上这500mb,否则container不会分配堆外这500mb的内存?看网上说3.0以上就不用加了。 回答:对的,堆外就是JVM heap以外的内存,以前的话,yarn把这部分内存算在container里面,现在不算在container里面了,不过这样其实有风险,因为堆外内存大小,对于yarn来说透明了,如果在运行时,Spark Job跑着跑着,发现OS根本分配不了500mb,那这个事情yarn是不负责的。 4、task会在哪些场景申请和释放内存呢?只是shuffle的场景吗?transformer场景会吗? 回答:凡是利用到AppendOnlyMap,PartitionPairBuffer这两个数据结构的计算,都要申请、释放内存,跟算子没啥关系哈,主要是shuffle write阶段的计算。
作者回复: 1. persist只能用来指定存储模式,memory还是disk,但不管什么缓存,都只能消耗Storage Memory 2. 对的,off heap必须显示开启才行。一旦开启off heap,作业会优先用off heap 3. 这个比较难,off heap、on heap,是以作业为控制粒度的,不是以task为控制粒度,也就是说,一个作业,要么都用off heap,要么都用on heap,不存在一个作业内部不同task,有的用堆外、有的用堆内。这个实现机制其实是有优化空间的~ 4. 这个参数,我理解是用来blacklist executors用的,也就是当一些executors频繁失败,spark会把他们标记到blacklist黑名单,避免下次DAGScheduler把任务调度到标记到blacklist的executors上面去。
作者回复: 是的,不过要显示地(Explicitly)指定存储级别:OFF_HEAP rdd.persist(OFF_HEAP)
作者回复: 和数据源存在哪儿关系不大哈,主要看它用于哪类计算,如果是纯运算,那就是execution memory;如果缓存下来,就是storage memory。取决于你的计算负载类型~