spark 动态预加载数据_Spark内存管理与调优

Spark是基于内存的大数据计算引擎,因此,在编写Spark程序或者提交Spark任务的时候,要特别注意内存方面的优化和调优。Spark官方也提供了很多配置参数用来进行内存或CPU的资源使用,但是为什么我们要进行这些参数的配置,这些参数是怎么影响到任务执行的,本篇文章将从Spark内存管理的原理方面进行分析。

一、JVM内存

1.JVM内存区域划分

因为Spark任务最终是运行在java虚拟机里面的,所以这里先分析一下JVM的内存区域划分。JVM的运行时内存划分主要包括以下几类:

6503c2af3ca7c1148439fb88e84df3c9.png

程序计数器:程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。

Java栈:同计数器也为线程私有,生命周期与相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。

本地方法栈:本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。

方法区:方法区同堆一样,是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。

:对于大多数应用来说,堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。

JAVA堆内存管理是影响性能主要因素之一。堆内存溢出是JAVA项目非常常见的故障,因此,必须先了解下JAVA堆内存是怎么工作的。

2.JVM内存模型

从JVM内存模型的角度来看堆内内存:

2ab51e156f817ed1bab708d086245459.png

对于堆内内存,又分为工作内存和主内存,工作内存属于线程私有,主内存数据线程公有,例如可以存储一些全局变量等数据。所以当线程公有的时候,如果线程1和线程2同时进行某个共享变量的读写的时候,就会出现数据异常,因此内存模型会涉及到三个概念:可见性、原子性和指令重排序(非本文重点可自行学习)。

二、Spark内存

了解了JVM的内存区域划分和内存模型,就不难理解Spark的内存管理了。Spark程序最是运行在JVM里面的,因此,就需要通过管理好JVM里的内存从来提升Job的运行效率。如上所述,运行时效率主要与堆内存有关,堆内存的概念涉及堆内内存和堆外内存:

  • 堆内内存是JVM中的堆内存,例如Spark程序的Driver和Executor都运行在堆内内存;
  • 堆外内存是Jvm运行时所在的server节点的操作系统的一部分内存

1.Spark内存管理策略

那么Spark是如何对这两种内存进行管理的呢?Spark提供了两种内存管理的策略:一种是静态内存管理策略,另一种是统一内存管理策略。(可以通过Spark源码的MemoryManager来找到它的两种实现策略)

ab9876dced80d5c1c808f0504bf686b4.png

这两种内存管理策略本质上两套不同的(对(堆内内存和堆外内存的)执行内存和存储内存的)划分方案。

那么执行内存和存储内存又是什么呢?这就要看Spark的堆内内存和堆外内存的划分方案了。不管是堆内内存还是堆外内存,都至少包含两个重要的内存区域:存储内存和执行内存。因此,Spark的两套内存管理策略就是针对这两种内存划分展开的。

30eda8561d3cc15615930292fae88ca3.png

2.静态内存管理策略

静态内存管理机制是,对于存储内存和执行内存及其他的小部分内存的大小,在Spark的任务运行期间是固定的,用户在进行任务提交前进行配置即可。

26e5a0cc82160fb456041597200a9455.png
静态内存管理-堆内内存划分策略

fb0355879919f7fbd176d8252c2af7e0.png
静态内存管理-堆外内存划分策略

3.统一内存管理模型

在Spark版本1.6之前,Spark的内存划分策略都是静态内存管理。随着硬件技术的快速发展,内存容量的提升,静态内存管理的方式已经不太适应新的硬件水平,Spark又推出了统一内存管理的策略。

统一内存管理与静态内存管理大致是一样的,只是在静态内存管理的基础上,加入了“动态占用机制”。对于运行期间,存储内存和执行内存不够的情况下,可以临时占用对方的内存,这就使得内存使用的灵活度大大提高,内存使用率也大大提高。

38eaf84e787a7e6282366fae65a80451.png
统一内存管理-堆内内存划分策略

b647188fa13dac402c010dfe0ca2b586.png
统一内存管理-堆外内存划分策略

内存一旦可以临时占用,又会出现很多问题,例如如果某个时刻,存储内存和执行内存都想占用对方的内存怎么办,或者执行内存首先占用了存储内存,导致存储内存在需要的时候不够用,这个时候是否可以让执行内存强制释放占用的内存?

鉴于以上问题,Spark根据两种内存对于任务的影响程度划分了占用优先级。由于执行内存会有一些中间数据或shuffle数据,这部分数据如果丢失对于整个任务的正确性都是至关重要的,而存储内存中存的大多是一些RDD缓存数据,丢失了顶多会对任务的性能有影响,因此执行内存的优先级要高于存储内存。也就是如果执行内存占用了存储内存,存储内存就得乖乖等着。

396bc853fb4f18eaf4b9d895a7beb910.png

4.Spark任务与堆内存

通过以上,你可能已经了解了JVM内存中最重要的堆内存相关的两个概念,以及Spark是如何进行这两种内存管理的。那Spark的一个具体的任务跟这两种内存有什么关系呢?

9f1a7713dffa43bdd1fc114869ab5d56.png

上图展示了一个worker节点上的任务与内存的关系。可以看到一个worker节点有两个Executor,每个Executor都是一个进程,每个task是一个线程。Executor的内部会有一块堆内内存,也就是主内存,这块内存是每个task共享的;两个Executor进程之外还有一块堆外内存,是供两个进程共享的。那么,堆外内存是什么时候使用呢?

我们在进行Spark任务提交的时候,可能会给每个Executor分配内存,例如每个Executor分配了1G的内存。但是如果100个Executor中,有99个都够用这1G内存,只有1个Executor不够用,这时候如果通过配置,将所有的Executor的内存统一调高,会造成大量的内存浪费。因此,这个时候就是堆外内存派上用场的时候了,可以允许内存不够用的Executor借用堆外内存来完成自己的任务处理。

三、总结

以上主要从内存原理的角度介绍了为什么要进行内存调优以及内存调优从本质上是影响了什么。全文围绕堆内内存和堆外内存以及执行内存和存储内存几个概念进行分析,最后通过简单地Spark任务来分析这几个概念在实际实例中的关联。

通过本文希望你下次配置内存参数的时候,能够知道你配置的参数到底是到了内存趋于的哪一块,这样才能更好地做好调优工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值