文章目录

  • ​​spark outline​​
  • ​​Spark基于Yarn的Cluster提交作业流程​​
  • ​​Spark 任务的划分​​
  • ​​Spark 任务的提交流程​​
  • ​​Spark 通信流程​​
  • ​​Spark Shuffle​​
  • ​​Shuffle阶段的划分​​
  • ​​task个数确定​​
  • ​​reduce task数据拉取过程​​
  • ​​Shuffle类型​​
  • ​​HashShuffle​​
  • ​​SortShuffle​​
  • ​​spark的Shuffle和Hadoop的shuffle异同?​​
  • ​​怎么提高shuffle的效率​​
  • ​​spark 内存管理​​
  • ​​spark 内存种类划分​​
  • ​​spark 内存空间划分​​

spark outline

​​大纲目录​​

Spark基于Yarn的Cluster提交作业流程

Spark 内核_数据

  1. 代码打成jar包上传到集群后,执行脚本命令spark-submit
  2. 执行类 SparkSubmit,这时会创建一个进程(这个进程在控制台黑窗口中可以看到:SparkSubmit)
  3. 通过类中的解析参数的方法 parseArguments(args)去解析参数,参数中包括 --master --class 等信息
  4. 参数解析完毕后准备提交,然后判断是mesosCluster模式、Client模式还是yarnCluster模式,若模式为yarnCluster,则会构建yarnClient和RM进行通信,然后由yarnClient向RM提交 Application, Application中其实就是一些指令:例如请求RM 启动 AM
  5. RM会给找一个空闲的NM启动AM,AM启动后创建一个客户端(AMRMClient) ,用来和RM进行通信
  6. 当运行到runDriver()方法时,会根据参数启动一个Driver线程,Driver会去读取用户程序代码,初始化 SparkContext 上下文环境, 判断sc(SparkContext)是否为空,​​并同时开始初始化 RPCEnv 通信环境​
  7. 因为拿到了上下文环境,所以AM客户端(AMRMClient)就会知道该程序需要用多少资源,然后和RM进行通信申请资源
  8. RM返回资源列表给AM客户端,AM这边会在线程池中创建ExecutorRunable对象,由该对象创建NMClient,创建NMClient的目的是和其它NM进行通信来,创建Executor
  9. 和其它NM建立连接后,NM首先会启动一个ExecutorBackend后台进程(这个进程在控制台黑窗口中可以看到:CoarseGrainedExecutorBackend),​​随之设置RPCEnv 通信环境​
  10. 通信环境设置成功以后,会向Driver请求注册Executor,Driver返回应答后开始真正创建 Executor 计算对象,​​随之继续执行用户编写的程序代码​

​总结:​

以上10步则为申请资源,即把driver和executor所需要的资源都准备好,然后开始真正执行用户编写的程序代码了,这也就有后续的任务的切分,任务的提交等操作了

​注意:​​以上有3处标红的地方,前2处是涉及到Driver和Executor之间是怎么进行通信的,该过程也非常复杂,如果展开叙述篇幅过大,下边会开一个标题专门叙述,而最后一处红色标记,也会开标题专门叙述

以上的叙述的逻辑宏观描述:

申请资源—>Driver线程被阻塞—>反射执行main()方法,初始化SparkContext上下文环境—>资源继续申请,申请成功—>执行用户程序后续代码

spark client与spark cluster的区别:

区别就是Driver运行在集群内部,还是集群外部(客户端)

为什么不选择把Driver运行在客户端

Driver运行在客户端,不便于后期Executor会和Driver频繁的信息交换,因为其过程会大量涉及网络IO等

Spark 任务的划分

​​Spark Job 划分​​

Spark 任务的提交流程

Spark 内核_客户端_02

  1. 在Driver初始化SparkContext环境时,同时也初始化了RPCEnv通信环境、TaskScheduler、DAGScheduler等对象
  2. 当Executor 计算对象创建完毕后,Driver就开始执行用户编写的程序代码了,当在代码中遇到行动算子时会触发任务的执行,然后按照rdd之间的血缘关系形成一个DAG有向无环图,并发送给 DAGScheduler
  3. DAGScheduler根据其中宽依赖的个数划分stage,再根据最后一个RDD的分区数把每个stage划分成一个个的task,然后把这些task包装成taskset,发送给TaskScheduler,TaskScheduler会先对taskset进行包装,包装成taskmanager,将taskmanager放入任务池的调度器中,并在任务池当中进行本地化级别的判断,本地化级别简单来说就是—移动数据,还是移动计算逻辑到某结点上,选择好本地化级别后,会将任务池当中的任务数据取出并序列化,通过Driver端RPCEnv通信模块发送到Exector计算结点上,然后Exector会根据接收到的信息,开启相应的task进行数据计算

Spark 通信流程

简介:

该通信是用于Driver和Executor之间是怎么进行通信的

Spark 通信架构前后共有2种,一种是Akka,另一种是的Netty

spark 使用的通信框架大致演变过程

在 Spark0.x系列中, 通信框架是Akka

在 Spark1.3 中引入了 Netty 通信框架,引入这个框架的目的是为了解决Shuffle的大数据传输问题

Spark1.6 中 Netty 完全实现了 Akka 在Spark 中的功能,所以从Spark2.0.0, Akka 被移除了

为什么使用Netty框架?

因为它不仅采用了异步非阻塞式IO,即AIO,也解决了Shuffle的大数据传输问题

通信IO大致分为3类

  1. BIO阻塞式IO
  2. NIO :非阻塞式IO
  3. AIO:异步非阻塞式IO

缺点:linux对AIO支持不够友好,但是服务器一般都是linux系统,所以又借用了Epoll的方式来模仿AIO操作

Spark RPC通信流程

​这里以executor向driver注册为例,大致描述一下通信流程的:​

Spark 内核_客户端_03

  1. Driver端和ExeCutor端各有N个OutBox发件箱,和一个Inbox收件箱
  2. 由于双方各自既是消息的发送方又是消息的接收方,所以它们既有客户端又有服务器端
  3. 当NM的通信环境初始化好以后,Executor会通过ask方法向Driver请求注册,ask请求信息需要先经过自己的消息分发器Dispatcher分发到Outbox发件箱中,然后通过发件箱发送到Driver的Inbox收件箱中
  4. Driver接收到消息以后,会对消息的类型进行判断,判断出对方的消息类型为ask,那么则需要自己进行应答,应答消息经由自己的消息分发器先分发到Outbox发件箱中,然后发送到Executor服务器端的inbox收件箱中
  5. Executor接收到消息以后会先对消息类型进行判断,判读以后就启动task开始干活了

通信过程涉及很多未提及的专业名称,但是不重要,只需要掌握大概即可

Spark 内核_spark_04

​组件概念解释:​

  1. RpcEndpoint:RPC端点,Spark针对每个节点(Client/Master/Worker)都称之为一个Rpc端点,且都实现RpcEndpoint接口,内部根据不同端点的需求,设计了不同的消息和不同的业务处理逻辑,如果需要send message or receive message 则调用Dispatcher
  2. RpcEnv:RPC上下文环境,每个RPC端点运行时依赖的上下文环境称为RpcEnv
  3. Dispatcher:消息分发器,针对于RPC端点需要发送消息或者从远程RPC接收到的消息,分发至对应的指令收件箱/发件箱。如果指令接收方是自己则存入收件箱,如果指令接收方不是自己,则放入发件箱
  4. Inbox:指令消息收件箱,一个本地RpcEndpoint对应一个收件箱,Dispatcher在每次向Inbox存入消息时,都将对应EndpointData加入内部ReceiverQueue中,另外Dispatcher创建时会启动一个单独线程进行轮询ReceiverQueue,进行收件箱消息消费
  5. RpcEndpointRef:RpcEndpointRef是对远程RpcEndpoint的一个引用。当我们需要向一个具体的RpcEndpoint发送消息时,一般我们需要获取到该RpcEndpoint的引用,然后通过该应用发送消息
  6. OutBox:指令消息发件箱,对于当前RpcEndpoint来说,一个目标RpcEndpoint对应一个发件箱,如果向多个目标RpcEndpoint发送信息,则有多个OutBox。当消息放入Outbox后,紧接着通过TransportClient将消息发送出去。消息放入发件箱以及发送过程是在同一个线程中进行
  7. RpcAddress:远程RpcEndpointRef的Host + Port
  8. TransportClient:Netty通信客户端,一个OutBox对应一个TransportClient,TransportClient不断轮询OutBox,根据OutBox消息的receiver信息,请求对应的远程TransportServer
  9. TransportServer:Netty通信服务端,一个RpcEndpoint对应一个TransportServer,接受远程消息后调用Dispatcher分发消息至对应收发件箱

​组件之间工作流程解释:​

看着图自我总结吧

Spark Shuffle

Shuffle阶段的划分

Spark 内核_客户端_05


当代码中有shuffle算子时,会做一次阶段划分,然后分为2个阶段

Spark 内核_数据_06

​补充:​​当代码中有多个shuffle算子时,最后一个阶段称之为ResultStage,这个阶段之前的统称为ShuffleMapStage

task个数确定

Spark 内核_客户端_07

reduce task数据拉取过程

  1. map task 执行完毕后会将计算状态以及磁盘小文件位置等信息封装到MapStatus对象中,然后由本进程中的MapOutPutTrackerWorker对象将mapStatus对象发送给Driver进程的MapOutPutTrackerMaster对象
  2. 在reduce task开始执行之前会先让本进程中的MapOutputTrackerWorker向Driver进程中的MapoutPutTrakcerMaster发动请求,请求磁盘小文件位置信息
  3. 当所有的Map task执行完毕后,Driver进程中的MapOutPutTrackerMaster就掌握了所有的磁盘小文件的位置信息。此时MapOutPutTrackerMaster会告诉MapOutPutTrackerWorker磁盘小文件的位置信息
  4. 步骤1,2,3完成之后,由BlockTransforService去某个Executor0所在的节点拉数据,默认会启动五个子线程。每次拉取的数据量不能超过48M,将拉来的数据存储到Executor内存的20%内存中

Shuffle类型

Spark 内核_客户端_08

HashShuffle

​Spark 2.0 版本中, Hash Shuffle 方式己经不再使用​

Spark 内核_客户端_09

(1)未优化的HashShuffleManager

​特点:​​下一个stage的task有多少个,当前stage的每个task就要创建多少份磁盘文件

比如说:下一个 stage 总共有 100 个 task,当前 stage 有 50 个 task,那么需要创建50*100=5000个小文件

​优点:​​​没有排序
​​​缺点:​​小文件过多,会引起大量的磁盘IO

流程图:

Spark 内核_数据_10

(2)优化后的HashShuffleManager

​特点:​​第一批并行执行的每个task都会创建一个shuffleFileGroup(组内文件的数量与下游 stage 的 task 数量是相同),以后的第二批、第三批等的task都会复用第一批task创建的shuffleFileGroupshuffleFileGroup

​优点:​​​没有排序
​​​缺点:​​依旧不能有效缓解磁盘小文件创建数量

流程图:

Spark 内核_客户端_11

SortShuffle

Spark 内核_数据_12

(1)普通运行机制(有排序)

​优点:​​减少了小文件

流程图:

Spark 内核_spark_13

  1. map task 首先会将数据写入到内存数据结构里面,如果是 reduceByKey 这种聚合类的 shuffle 算子,那么就会写入到 Map 数据结构,一边通过 Map 进行聚合,一边写入内存,如果是 join 这种普通的 shuffle 算子,那么就会写入 Array 数据结构,然后直接写入内存,内存数据结构默认是5M
  2. 在数据写入内存后,会有一个定时器,不定期的去估算这个内存结构的大小,当内存结构中的数据超过5M时,它首先会尝试申请内存,如果申请成功,继续写入内存,如果申请失败,那么就会溢写数据到磁盘文件(比如现在内存结构中的数据为5.01M,那么它会申请 5.01*2-5=5.02M 内存)
  3. 在溢写之前会对内存中的数据进行快排
  4. 排序好的数据会以batch(一个batch是1万条数据)的形式先先写入java的内存缓冲区(32k),待缓冲区满后,写入磁盘
  5. map task执行完成后,磁盘中可能会产生大量小文件,然后对这些小文件进行一次归并排序,合并成一个大文件,同时生成一个索引文件
  6. reduce task去map端拉取数据的时候,首先解析索引文件,根据索引文件再去拉取自己指定分区的数据,存入内存,内存不够,溢写磁盘(内存大小48m)

(2)bypass运行机制(无排序)

不排序的条件:

Spark 内核_spark_14


​特点:​​该过程的磁盘写机制其实跟未经优化的 HashShuffle是一模一样的,都要创建数量惊人的磁盘文件,只不过是会在最后做一个磁盘文件的合并而已

流程图:

Spark 内核_数据_15

spark的Shuffle和Hadoop的shuffle异同?

  • 相同点

从宏观上看,2者没啥区别

(1)都是先在Map端对数据进行处理,然后在Reduce端对数据进行处理
(2)都有对数据进行分区,和提前进行预聚合的功能

  • 不同点

从细节上看

  1. shuffle的分类不同: spark的shuffle分为2大类,第一类是HashShuffle,第二类是SortShuffle。HashShuffle又分为未优化的HashShuffle和未优化的HashShuffle(Spark 2.0 版本中, Hash Shuffle 方式己经不再使用);SortShuffle也分为可排序的和不可排序的Shuffle,而mr的shuflle只有一种必须排序的shuffle。在一些求和和求平均值场景下,排序只会带来不必要的资源消耗
  2. 若都为可排序的shuffle:mr 的map阶段过后,只有一个数据文件,而spark不仅有数据文件,而且还有一个索引文件,由于有了索引文件,那么数据文件就可以相对很大,数据文件越大,那么后续的reduce task数量就越少
  3. mr的shuffle前中后有着更为细致的划分,例如在map端可细划分为Read、Map、Collect、Spill(溢写)Merge阶段,reduce端可细划分为pull、merge、reduce阶段,而spark的shuffle没有明显的阶段划分,只有在遇到行动算子时,才会去真正的拉取数据

怎么提高shuffle的效率

提高shuffle性能最有效的办法,提前进行预聚合,有预聚合功能的算子有

  • foldbykey
  • reducebykey
  • aggregatebykey
  • combinebykey

spark 内存管理

spark 内存种类划分

spark 内存分为堆内和堆外2种

  • 堆内内存(On-heap):建立在 JVM 的内存管理之上,受JVM统一管理
  • 堆外内存(Off-heap):向操作系统借的,直接受操作系统管理,不由JVM统一管理

spark 内存空间划分

Spark 1.6 之前,堆内内存使用的是静态内存管理机制,Spark 1.6 之后,使用的是动态内存管理机制,动态内存管理机制和静态内存管理的​​最大区别在于​​,动态内存管理机制下,存储内存和执行内存共享同一块空间,当某一方的内存不足时,可以动态占用对方的空闲内存。

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

Spark 内核_客户端_16

Spark 内核_数据_17

统一内存管理的​​堆外内存结构如图:​

Spark 内核_spark_18


堆外内存的作用:

  1. 为了提高 Shuffle 时排序的效率,Spark 引入了堆外(Off-heap)内存,来直接存储排序好的数据
  2. 堆外内存不受Java虚拟机管理,那么就可以很有效的减少垃圾回收对应用的影响

统一内存最重要的优化​​在于动态占用机制​​,其规则如下:

Spark 内核_spark_19

  1. 存储内存和执行内存双方的空间都不足时,则存储到硬盘;若有一方内存空间不足而对方空余时,可借用对方的空间(存储空间不足是指不足以放下一个完整的 Block)
  2. 存储内存占用执行内存的空间后,执行内存不够时,可让存储内存将占用的部分转存到硬盘,然后”归还”借用的空间
  3. 执行内存占用存储内存后,存储内存不够时,有肯无法让执行内存”归还”,因为要保证数据结果的准确性