Spark 内核概述
Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制、Spark的任务调度机制、Spark的内存管理机制、Spark核心功能的运行原理等。熟练掌握Spark内核原理能够帮助我们更好的完成Spark代码设计,并且在项目运行时出现问题可以快速的锁定问题的症结。
Spark 核心组件回顾
Cluster-Manager(Master,ResourceManager)--Spark的集群管理器,主要负责对整个集群资源的分配与管理。
Cluster Manager 在 Yarn 部署模式下为 ResourceManager; 在 Mesos 部署模式下为 Mesos Master; 在 Standalone 部署模式下为 Master。
Cluster Manager 分配的资源属于一级分配, 它将各个 Worker 上的内存, CPU 等资源分配给 Application, 但并不负责对 Executor 的资源的分配。
Worker(Worker,NodeManager:Yarn部署模式)--主要负责以下工作:
- 将自己的内存、CPU等资源通过注册机制告知Cluster-Manager;
- 创建Executor进程;
- 将资源和任务进一步分配给Executor;
- 同步资源信息、Executor状态信息给ClusterManager等;
Driver --Spark的驱动器节点,用于执行 Spark 任务中的main方法、实际代码的执行工作。
Driver在Spark作业执行时负责以下工作:
- 将用户程序转化为Job;
- 在Executor之间调度Task;
- 跟踪Executor的执行情况;
- 通过UI展示查询运行情况;
Executor --负责运行Job中具体的Task,Task之间相互独立。Spark应用启动的时候Executor节点同时被启动,并且伴随Spark应用的整个生命周期。如果Executor节点发生了故障,Spark应用可以继续执行,会将出错节点上的任务调度到其他Executor上继续执行。
Executor核心功能
- 负责运行组成spark应用的Task,并将结果返回给Driver
- Executor通过块管理器Block-Manager为用户程序中需要缓存的RDD提供内存式存储。RDD的数据是直接缓存到Executor进程内的,所以Task可以在运行时充分利用缓存数据加速运算。
Application --用户使用spark提供的API编写的应用程序。
- Application通过spark API进行RDD的转换和DAG的构建,并通过Driver将Application注册到Cluster-Manager;
- Cluster-manager根据Application的资源需求将Executor、CPU、memory分配给Application;
- Driver将Executor等资源分配给每一个Task,Application最后通过Driver通知Executor运行任务;
下面看一张图理解一下spark的通用运行流程(不管何种模式,只是核心步骤)
- 任务提交后,都会先启动Driver程序;
- 随后Driver想集群注册应用程序;
- 之后Cluster-Manager根据此任务的配置文件分配Executor并启动该Application;
- 当Driver所需的资源全部满足后,Driver开始执行main()函数,spark转换为懒执行,当执行到action算子时开始反响推算,根据宽依赖进行stage的划分,随后每一个stage对应一个TaskSet(包含多个task);
- 根据本地化原则,Task会被分发到指定的Executor去执行,在任务执行的过程中,Executor也会不断与Driver进行通信,报告任务运行情况;
Spark 通信框架概述
spark内置RPC框架
spark在各个组件之间的消息互通、用户文件与Jar包上传、节点间的shuffle过程、Block数据的复制与备份等地方都要涉及通信。在spark1.3版本之前各组件之间通过Akka通信,在spark1.3的时候引入了Netty通信框架,Akka要求message发送端和接收端有相同的版本, 所以为了避免 Akka 造成的版本问题,并给用户的应用更大灵活性,决定使用更通用的 RPC 实现,也就是现在的 Netty 来替代 Akka。从spark2.0开始Akka被彻底移除。
Actor模型
Spark通信框架中各个组件(Client/Master/Worker)可以认为是一个个独立的实体,各个实体之间通过消息来进行通信。
Endpoint(Client/Master/Worker)有 1 个 InBox 和 N 个 OutBox(N>=1,N取决于当前 Endpoint 与多少其他的 Endpoint 进行通信,一个与其通讯的其他Endpoint 对应一个 OutBox),Endpoint 接收到的消息被写入 InBox,发送出去的消息写入 OutBox 并被发送到其他 Endpoint 的 InBox 中。
Spark Netty 通信架构解析
Netty 官网: https:// netty.io/
- RpcEndpoint:RPC 端点。
Spark 针对每个节点(Client/Master/Worker)都称之为一个 RpcEndpoint ,且都实现 RpcEndpoint接口,内部根据不同端点的需求,设计不同的消息和不同的业务处理,如果需要发送(询问)则内部调用 Dispatcher 的对应方法;
RpcEndpoint 接收消息;
RpcEndpointRef 发送消息;
RpcEndpointRef的具体实现类是: NettyRpcEndpointRef
- RpcEnv: Rpc 上下文(Rpc 环境)
每个RpcEndpoint运行时依赖的上下文环境称为 RpcEnv
- Dispatcher:消息分发器
RPC 端点需要发送消息或者从远程 RPC 端点接收到的消息,分发至对应的指令收件箱/发件箱。
如果指令接收方是自己则存入收件箱,如果指令接收方不是自己则放入发件箱
// class NettyRpcEnv
- Inbox:指令消息收件箱。
一个本地 RpcEndpoint 对应一个收件箱
- RpcEndpointRef:RpcEndpointRef 是对远程 RpcEndpoint 的一个引用。
当我们需要向一个具体的 RpcEndpoint 发送消息时,一般我们需要获取到该RpcEndpoint 的引用,然后通过该引用发送消息。
- OutBox:指令消息发件箱。
对于当前 RpcEndpoint 来说,一个目标 RpcEndpoint 对应一个当前的发件箱,如果向多个目标 RpcEndpoint 发送信息,则有当前会有多个 OutBox。当消息放入 Outbox 后,紧接着通过 TransportClient 将消息发送出去。消息放入发件箱以及发送过程是在同一个线程中进行;
- RpcAddress:表示远程的RpcEndpointRef的地址,Host + Port
- TransportClient:Netty通信客户端
一个 OutBox 对应一个 TransportClient,TransportClient 不断轮询OutBox,根据 OutBox 消息的 receiver 信息,请求对应的远程 TransportServer;
- TransportServer:Netty 通信服务端
一个 RpcEndpoint 对应一个 TransportServer,接受远程消息后调用 Dispatcher 分发消息至自己的收件箱,或者对应的发件箱;
- 再来一张图感受一下
Spark 集群启动流程分析(本章分析Standalone模式的启动流程)
- start-all.sh脚本,实际是执行java -cp Master和java -cp Worker;
- Master启动时首先创建一个RpcEnv对象,负责管理所有通信逻辑;
- Master通过RpcEnv对象创建一个Endpoint,Master就是一个Endpoint,Worker可以与其进行通信;
- Worker启动时也是创建一个RpcEnv对象;
- Worker通过RpcEnv对象创建一个Endpoint;
- Worker通过RpcEnv对象建立到Master的连接,获取到一个RpcEndpointRef对象,通过该对象可以与Master通信;
- Worker向Master注册,注册内容包括主机名、端口、CPU Core数量、内存数量;
- Master接收到Worker的注册,将注册信息维护在内存中的Table中,其中还包含了一个到Worker的RpcEndpointRef对象引用;
- Master回复Worker已经接收到注册,告知Worker已经注册成功;
- Worker端收到成功注册响应后,开始周期性向Master发送心跳。
- start-master.sh Master 启动脚本分析
启动
- start-slaves.sh Worker 启动脚本分析
启动
- Master 启动源码分析
private
- RpcEnv的创建
真正的创建是调用NettyRpcEnvFactory的create方法创建的.
创建 NettyRpcEnv的时候, 会创建消息分发器, 收件箱和存储远程地址与发件箱的 Map
- RpcEnv.scala源码分析
def
- Master伴生类(Master 端的 RpcEndpoint 启动)
Master是一个RpcEndpoint,他的生命周期方法是:constructor -> onStart -> receive* -> onStop
- onStart 主要代码片段
// 创建 WebUI 服务器
- Worker启动源码分析
org.apache.spark.deploy.worker.Worker
- Worker伴生对象--启动流程基本和 Master 一致
private
- Worker伴生类 --onStart 方法
override
- registerWithMaster( ) 方法关键代码:
// 向所有的 Master 注册
- tryRegisterAllMasters() 方法
private
- registerWithMaster 方法
private
- Master的receiveAndReply方法
// 处理 Worker 的注册信息
- worker的handleRegisterResponse方法
case
- Worker的receive方法:
case
- Master的receive方法:
case
---------Worker启动完成-------