一些名词概念

AM : ApplicationMaster
RM : ResourceManager
NM : NodeManager
Backend : 后台
RpcEnv : RPC 进程和进程的通信协议
RpcEndpoint : 终端 constructor -> onStart -> receive* -> onStop RpcEndpointRef :终端引用 NettyRpcEnv
RpcEndpointAddress
NettyRpcEndpointRef ThreadSafeRpcEndpoint Inbox BIO, NIO, AIO

以yarn-cluster模式为例源码分析作业提交流程

##spark-yarn-cluster模式
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--num-executors 2 \
--master yarn \
--deploy-mode cluster \
./examples/jars/spark-examples_2.11-2.1.1.jar \
100

##spark-yarn-client模式

bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--num-executors 2 \
--master yarn \
--deploy-mode client \
./examples/jars/spark-examples_2.11-2.1.1.jar \
100

Spark-submit提交源码解析

执行spark-submit实际上执行的是$SPARK_HOME/spark-class -->执行一个java类  java  org.apache.spark.launcher.Main调它的main方法(它就是检查参数、构建CommandBuiler指令)--->最终真正要执行 这个类 org.apache.spark.deploy.SparkSubmit xxx是命令行参数,它就会启动一个叫SparkSubmit进程

SparkSubmit类,对命令行参数进行了封装,准备提交环境,不是集群模式它会拿到指令中的--class所传的类, if (isYarnCluster)-->> org.apache.spark.deploy.yarn.Client 

利用反射通过类名获取类的信息,利用反射通过类信息获取Main方法,调用Client的main方法: Client的调用只是一个普通方法的调用
根据yarn的配置进行初始化创建yarn客户端准备去跟yarn打交道(要有yarn的server集群), yarnClient.start()启动客户端,获取一个appId,yarn的全局id,根据id可以查询它的所有信息;
Client向yarn的RM提交应用, 集群模式通过submitApplication来完成,启动JAVA_HOME/bin/java org.apache.spark.deploy.yarn.ApplicationMaster这个进程类(主要是为了与Yarn做关联)
这些commands上边封装好的指令|类都提交给yarn,由yarn来执行,这时Client客户端就结束了;
spark-submit 它实际上调用的是"${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit (java中的类)
   spark-class中: RUNNER="${JAVA_HOME}/bin/java" ##执行java.exe-->类 -->相当于启动一个JVM -->就是启动一个进程Process(进程中有独立内存、线程)
   "$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH"(#就是类路径) org.apache.spark.launcher.Main <=>等同于执行Main这个类,调用main方法
   build_command()构建指令; 最终是要执行SparkSubmit ##className.equals("org.apache.spark.deploy.SparkSubmit")
org.apache.spark.launcher.Main
org.apache.spark.deploy.SparkSubmit --xx等参数
SparkSubmit类

          // 对命令行参数进行封装
-- . new SparkSubmitArguments(args) //命令行参数包含master、deployMode、executorMemory等 ;在scala中,类{ 类体or构造函数体 },参数中类体,类初始化时都会执行
        parse(args.asJava)类体中;parse方法做了正则的匹配 Pattern.compile("(--[^=]+)=(.+)") ==> handle (源码326)方法对传的命令行参数做模式匹配;
          SparkSubmit类,appArgs.action.match{ SUBMIT| ... }模式匹配,action在SparkSubmitArguments做了封装  action = Option(action).getOrElse(SUBMIT)
-- . submit(SparkSubmit中的方法)
// 准备提交环境
-- 2.1 prepareSubmitEnvironment -- 2.2 doRunMain(声明了但没有执行) isStandaloneCluster 和 其他模式都会走doRunMain方法 -- 2.3 runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)参数来自提交对环境准备 <--if (args.proxyUser != null) -- 2.3. ClassLoader : Thread.currentThread.setContextClassLoader(loader) //loader类加载器,把类放到集群的节点上去执行; // childMainClass(Client) : org.apache.spark.examples.SparkPi //不是集群模式它会拿到指令中的--class所传的类
// childMainClass(Clustor) : org.apache.spark.deploy.yarn.Client // if (isYarnCluster)集群模式
            // 反射:通过类名获取类的信息
-- 2.3. mainClass = Utils.classForName(childMainClass) //var mainClass: Class[_] = null,Class为类的全部信息;
// 反射:通过类信息获取Main方法
-- 2.3. val mainMethod = mainClass.getMethod("main", new Array[String]().getClass)
// 反射:调用main方法
-- 2.3. mainMethod.invoke(null, childArgs.toArray)
Client
      Client的调用只是一个普通方法的调用;如果是进程:java Client=》JVM=》Process; 如果是线程:Thread.start() ; 普通方法调用: method.invoke
// 对命令行参数进行封装
-- . new ClientArguments(argStrings) //在main方法中 -- . new Client(args, sparkConf).run() //它会去调用辅助构造方法-->一定会会去调用主构造方法
      private val yarnClient = YarnClient.createYarnClient //它去创建yarn客户端准备去跟yarn打交道(要有yarn的server集群)
        private val amMemory = if (isClusterMode) //ApplicationMaster的内存
         ...等等,主要是去构造属性、方法和对象
-- . client.run //创建好了对象.run
-- 3.1 submitApplication //this.appId = submitApplication() 提交应用;
      launcherBackend.connect() //Backend叫后台
           yarnClient.init(yarnConf) 根据yarn的配置进行初始化; yarnClient.start()启动客户端 ,目的是与yarn做交互;
           appId = newAppResponse.getApplicationId()//获取一个appId,yarn的全局id,根据id可以查询它的所有信息;
            yarnClient.submitApplication(appContext) //Client向yarn的RM提交应用,由submitApplication(抽象方法->实现类)来完成;
            rmClient.submitApplication(request); //跟RM做关联
// commands(Client): JAVA_HOME/bin/java org.apache.spark.deploy.yarn.ExecutorLauncher //不是集群模式 Spark-shell只能是client模式,不能是集群!!
// commands(Clustor):JAVA_HOME/bin/java org.apache.spark.deploy.yarn.ApplicationMaster//如果是集群模式,isClusterMode就把类对名字给amClass;启动一个ApplicationMaster进程
          //这些commands上边封装好的指令类都提交给yarn,由yarn来执行,这时Client客户端就结束了;
-- 3.1. createContainerLaunchContext //要去封装一个指令bin/java javaOpts ++ amArgs(这个参数中Seq(amClass)) ++
          启动一个进程执行ApplicationMaster
-- 3.1. createApplicationSubmissionContext
val containerContext = createContainerLaunchContext(newAppResponse)
val appContext = createApplicationSubmissionContext(newApp, containerContext) //它调用的是上边的;
// 向Yarn提交应用
-- 3.1. yarnClient.submitApplication(appContext) //把上边封装好的给yarnClient(相当于这些指令发给它);在yarn中执行,Client把类提交给yarn之后就完成了

yarn的调度流程 https://www.cnblogs.com/shengyang17/p/10321228.html

SparkSubmit进程(启动一个java虚拟机即启动一个进程名字就叫SparkSubmit) -->调用Client方法,YarnClient去创建yarn客户端并启动, submitApplication()---launcherBackend.connect() ;
Client的submitApplication(抽象方法->去找它的实现类YarnClientImpl)向RM提交应用完成之后; rmClient.submitApplication(request)来跟RM做关联;  

createContainerLaunchContext去启动一个进程执行ApplicationMaster;
yarnClient.submitApplication(appContext) //把上边封装好的给yarnClient(相当于这些指令发给它),Client把类提交给yarn之后就完成了
ApplicationMaster

    -- . main

        -- 1.1. new ApplicationMasterArguments(args) //同上,封装了--jar, --class等
-- 1.2. master = new ApplicationMaster(amArgs, new YarnRMClient)
      yarnConf
          heartbeatInterval心跳周期
          RpcEnv : RPC 进程和进程的通信协议
          RpcEndpoint : 终端   RpcEndpointRef:终端引用
-- 1.3. master.run
// 集群模式它运行的是Driver;如果不是集群模式运行的就是ExecutorLauncher
-- 1.3. runDriver() //isClusterMode
// 启动用户应用 就是提交的类
-- 1.3.1.1 startUserApplication()--->即启动一个Driver线程
// 从命令行参数中获取--class的值,然后获取main方法; 用类加载器加载类 userClass封装类名(从命令行中获取)
-- val mainMethod = userClassLoader.loadClass(args.userClass).getMethod("main", classOf[Array[String]])
  
-- new Thread("Driver").start() ==> --class.invoke(main)
  启动之前:userThread.setContextClassLoader(userClassLoader) //类加载器放进去
              userThread.setName("Driver") //真正的Driver是一个线程,只是把这个线程起个名字叫Driver
              userThread.start() //启动之后肯定会运行线程的run方法 mainMethod.invoke(main);创建SparkConetext的那个类叫Driver,就是你写的那个main方法
              虽然start,但什么时候执行不确定, userClassThread.join() //主线程不能结束,要等待它
                  rpcEnv = sc.env.rpcEnv构建环境对象
              
// 向Yarn注册AM
-- 1.3.1.2 registerAM()//client.
    allocator = client.register()
              allocator.allocateResources() 它们进行注册和分配,申请资源,看看哪个资源可用 allocateResources资源列表;
             val allocatedContainers = allocateResponse.getAllocatedContainers() //以container的形式把资源给你,allocatedContainers是列表 
// 分配可用资源 (移动数据不如移动计算(计算在Driver,数据在executor,任务task发给哪个节点),本地化级别)
-- handleAllocatedContainers
    本地化(计算和数据在同一个进程里)本地化(进程本地化即计算和数据中同一个进程 |节点本地化即一个节点有多个executor,发给另外一个executor|机架本地化,发给另外一个nodemanager)
-- runAllocatedContainers() //运行已经分配好的容器 -- launcherPool.execute(线程) <-- if(launchContainers) private val launcherPool = ThreadUtils.newDaemonCachedThreadPool //ThreadPool线程池 --> val threadPool = new ThreadPoolExecutor(),底层还是调用了jdk的线程池的操作
    launcherPool.execute(new Runnable{ run(方法 new ExecutorRunnable).run( 与nmClient(即nodemanager的关联) ) }) //启动一个线程来执行操作,从线程池里边去执行一个线程
-- ExecutorRunnable.run() //nmClient.init(conf)  nmClient.start()  startContainer() 要去连接NM -- nmClient.start()
// 启动容器
-- startContainer
// JAVA_HOME/bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend //执行器的后台,数据对接收处理,在NM中执行;
-- val commands = prepareCommand() -- nmClient.startContainer(container.get, ctx) //由NM(AM就在这里边)来启动这个容器;xtx就是commands指令 CoarseGrainedExecutorBackend 粗粒度的
-- . main -- 1.1. run //var executor: Executor = null //所谓的Executor是后台CoarseGrainedExecutorBackend的一个计算对象,就是一个属性;
  SparkEnv.createExecutorEnv()   env.rpcEnv.setupEndpoint()
env.rpcEnv.awaitTermination() //主线程不会结束,需要等待它的结束
           executor = new Executor() //在里边构建对象,跟CoarseGrainedExecutorBackend关系是很紧密的
            case LaunchTask(data) => executor.launchTask() //executor启动任务
         -- 1.1.1 env.rpcEnv.setupEndpoint("Executor", new CoarseGrainedExecutorBackend //把当前的Executor对象创建出来; setupEndpoint设置,它是个抽象方法=>实现类NettyRpcEnv
                override def setupEndpoint(): RpcEndpointRef = {dispatcher.registerRpcEndpoint(name, endpoint)} //把当前对象进行封装成name把它绑定中一起,形成注册效果就可以实现消息对发送和接收;
                registerRpcEndpoint完成注册

ApplicationMaster
master = new ApplicationMaster.run()即runDriver()->startUserApplication()--->即启动一个Driver线程(通过反射来获取类名).join()还没启动, 向Yarn注册AM--registerAM
client.register().allocateResources() 进行注册和分配,申请资源,看看哪个资源可用 allocateResources资源列表; allocateResponse.getAllocatedContainers() //以container的形式把资源给你
分配可用资源--handleAllocatedContainers--runAllocatedContainers() //运行已经分配好的容器 
if(launchContainers)--> launcherPool.execute(线程)->.run(ExecutorRunnable.run()),nmClient(即与nodeManager的关联)
nmClient.init(conf)  nmClient.start() nmClient.startContainer(container.get, ctx) //由NM(AM就在这里边)来启动这个容器
CoarseGrainedExecutorBackend 
所谓的Executor是后台CoarseGrainedExecutorBackend的一个计算对象,就是一个属性;
case LaunchTask(data) => new Executor().launchTask() //executor启动任务
    RpcEndpointAddress
NettyRpcEndpointRef
new EndpointData() //往终端里传数据 --> val inbox = new Inbox(ref, endpoint)//收件箱
-->inbox.synchronized {messages.add(OnStart)} //往消息里发送数据,给当前终端CoarseGrainedExecutorBackend发送OnStart消息
当前终端是可以通信的;ThreadSafeRpcEndpoint extends RpcEndpoint; RpcEndpoint终端是有生命周期的:
  The life-cycle of an endpoint is: constructor构造方法 -> onStart启动 -> receive* -> onStop关闭
CoarseGrainedExecutorBackend接收到消息就会去调用OnStart
       -->rpcEnv.asyncSetupEndpointRefByURI(driverUrl).flatMap 通过URI异步的去安装引用,通过一个地址得到远程对象对引用
通过driverUrl地址建立了两个executor之间的通信,而且是异步发送;
"Connecting to driver: " + driverUrl 找到Driver所在进程(AM),异步的这个操作等同于拿到了与ApplicationMaster的关系;
driver = Some(ref) 起完driver
ref.ask[Boolean](RegisterExecutor()) 发送RegisterExecutor请求,它会去反向注册
driver应该接收消息,它是一个线程不能接收,通过以下方法:
   SparkContext 中SchedulerBackend属性 它的实现类CoarseGrainedSchedulerBackend(driver这边的引用),而CoarseGrainedExecutorBackend是executor的后台引用;
它们之间通信,刚刚ref发送了消息,它就接收到--->case RegisterExecutor 在PartialFunction偏函数中(所有的模式匹配的case都是);
反向注册就是建立executor和driver之间的关系;
if (executorDataMap.contains(executorId)) {
executorRef.send(RegisterExecutorFailed("Duplicate executor ID: " + executorId))
//如果executorDataMap的数据集合中包含executorId,它向executor的引用发送注册失败的消息;else就启动
context.reply(true)
executorRef.send(RegisteredExecutor)//向远程的executor引用发送消息说在driver中已经注册好了可以启动了;
}

①脚本启动,执行SparkSubmit里面的main方法;

②反射获取Client类里边的main方法;

③封装并发送指令:bin/java ApplicationMaster

④选择一台NM机器启动AM

执行spark-submit实际上执行的是$SPARK_HOME/spark-class -->执行一个java类  java  org.apache.spark.launcher.Main调它的main方法(它就是检查参数、构建CommandBuiler指令)--->最终真正要执行 这个类 org.apache.spark.deploy.SparkSubmit xxx是命令行参数,它就会启动一个叫SparkSubmit进程

SparkSubmit类,对命令行参数进行了封装,准备提交环境,不是集群模式它会拿到指令中的--class所传的类, if (isYarnCluster)-->> org.apache.spark.deploy.yarn.Client 

利用反射通过类名获取类的信息,利用反射通过类信息获取Main方法,调用Client的main方法: Client的调用只是一个普通方法的调用
根据yarn的配置进行初始化创建yarn客户端准备去跟yarn打交道(要有yarn的server集群), yarnClient.start()启动客户端,获取一个appId,yarn的全局id,根据id可以查询它的所有信息;
Client向yarn的RM提交应用, 集群模式通过submitApplication来完成,启动JAVA_HOME/bin/java org.apache.spark.deploy.yarn.ApplicationMaster这个进程类(主要是为了与Yarn做关联)
这些commands上边封装好的指令|类都提交给yarn,由yarn来执行,这时Client客户端就结束了;

RM找一个节点NM来启动ApplicationMaster--java command;虽然只是不是集群模式它的类是ExecutorLauncher,但底层它同样还是调用的是ApplicationMaster.main;

集群模式 集群模式它运行的是Driver,启动用户应用 就是--class提交的类(通过类加载器);

new Thread("Driver").start() Driver只是ApplicationMaster进程中的一个线程起名叫Driver; 之后它运行的是--class.invoke(main)就是你写的那个有sparkContext的main方法的那个类
称为Driver类; 向Yarn的RM注册AM,并申请资源;RM会返回一个资源列表,去进行分配资源,资源是以container的方式;
分配可用资源 (移动数据不如移动计算(计算在Driver,数据在某个节点executor,driver来分配任务task发给哪个节点),本地化级别);
AM建立与NM的关联,向指定的资源NM发送指令; NM就会启动,它启动它的container容器;
//启动一个进程 JAVA_HOME/bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend //执行器的后台,数据对接收处理,在NM中执行,粗粒度的执行器后台
;所谓的Executor是后台CoarseGrainedExecutorBackend的一个计算对象,就是一个属性;
反向注册就是建立executor和driver之间的关系,告诉driver我有一个executor已经启动了;
向远程的executor引用发送消息说在driver中已经注册好了可以启动了;

RDD中的数据变成一个个分区的数据,一个个分区变成任务

RDD(对数据计算逻辑的 抽象,里边没有数据)提交

任务的划分、分配、执行

RDD

    -- . collect

        -- 1.1 SparkContext.runJob

            -- dagScheduler.runJob() //有向无环图,有放向不能形成一个环,血缘关系形成的一个有向无环图调度器;

                -- submitJob()

                    -- eventProcessLoop.post( JobSubmitted()) //它等同往里边放了一个样例类;发送消息JobSubmitted

                    -- dagScheduler.handleJobSubmitted() //接收到JobSubmitted消息

DAGScheduler

    -- . handleJobSubmitted

            // 划分阶段-Stage
-- 1.1 createResultStage //finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite) val job = new ActiveJob()构建job对象即一个作业;有多个行动算子即执行了多个作业;
// 提交阶段,有多少个阶段State val missing = getMissingParentStages(stage).sortBy(_.id) 这个阶段有没有上级即上一饿阶段Stage呢
-- 1.2 submitStage // 提交任务 ,如果有上一个阶段就提交上个阶段,如果还有上个阶段就继续提交上个阶段,如果没有就提交现阶段中的任务
-- 1.2. submitMissingTasks提交任务,missing表没有上级了 //if (missing.isEmpty) 如果上一个阶段为空没有,则submitMissingTasks提交task //如果有阶段就把上一个阶段给提交了 submitStage(parent)
// 将任务进行封装,进行调度 stage 模式匹配 ShuffleMapStage | ResultStage ,spark中只有两种模式匹配;
val tasks: Seq[Task[_]] 构建任务集合{对stage进行操作}
如果上ShuffleMapStage会计算分区partitionsToCompute.map <=>new ShuffleMapTask多少个分区变成多少个任务 -- 1.2. taskScheduler.submitTasks(TaskSet)//任务调度器提交任务<--if (tasks.size > 0), 把任务封装成一个TaskSet集合传进来
submitTasks的实现类TaskSchedulerImpl; backend.reviveOffers()
reviveOffers的实现类CoarseGrainedSchedulerBackend ->reviveOffers->makeOffers()-->launchTasks(scheduler.resourceOffers(workOffers)) // 启动任务
-- 1.2. launchTasks //拿到了一个个任务; val serializedTask = ser.serialize(task)序列化
executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))从executor中取出一个终端,从当前后台去找的executor的后台
发送一个LaunchTask启动任务的消息 CoarseGrainedExecutorBackend -- LaunchTask //receive接收到一个消息LaunchTask,表让executor执行这个任务 val taskDesc = ser.deserialize[TaskDescription](data.value)反序列化 -- executor.launchTask() //拿到任务,executor再去启动 --> val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,serializedTask)
// 运行任务 threadPool.execute(tr) //线程池在执行某个线程操作即TaskRunner extends Runnable -->run
-- task.run //释放内存更新状态等;
DAGScheduler
划分阶段 finalStage = createResultStage(finalRDD..);
ShuffleMapStage | ResultStage ,spark中只有两种模式匹配;
 taskScheduler.submitTasks(TaskSet)//任务调度器提交任务<--if (tasks.size > 0), 把任务封装成一个TaskSet集合传进来;
 

Spark通信架构

RPC通信:远程过程(服务)调用-(两个进程之间的通信)
RPC的实现有多种,其中有RMI,原理:
一个Process1中的client去调用它的service,要让Process2中的service去执行;通过socket,两个进程的service一定是个接口,那么client怎么去调用接口呢
通过动态代理(把接口代理为对象来使用),client通过代理对象Proxy与service去关联,代理对象会完成两个进程中socket的交互;

有封装好的框架可以使用;

Spark2.x版本使用Netty通讯框架作为内部通讯组件。spark 基于netty新的rpc框架借鉴了Akka的中的设计(随着版本升级已不再使用Akka,用Netty),它是基于Actor模型;

 Actor模型

Actor模型:
有多个对象,Object1, Object2 、Object3都给Object1发消息,都可以发消息(异步操作,发完该干嘛干嘛去;同步是发完之后不能做别的要等待它的返回),但消息的先后由自己决定
接收消息的一方Object1有个收件箱Inbox(消息不是马上执行的),它会去周期性的去取即轮询操作,有就去处理;
Object1从Inbox中查询到消息,发件箱Outbox(对应每一个对象),可以有多个发件箱可以同时发送
Outbox(Object2) ---> Object2中相应的要有收件箱Inbox(query),则Object1中要有Object2中地址的引用;
Actor模型中不叫Object了改叫EndPoint,Outbox(Object2)中的Object2引用改叫EndPointRef
Ref即远程的引用,要有地址去连接RpcAddress,终端和终端的引用互相发生关联;

Endpoint(Client/Master/Worker)有1个InBox和N个OutBox,每一个引用就是一个OutBox,多个OutBox可以同时发送消息

Netty 

NETT底层还是socket

IO中三种不同的处理方式:
BIO(最慢,阻塞式IO,读取文件时要等着不能去做别的事),
NIO(非阻塞式IO), 性能高,拿一个线程不断的去轮询,还可以去做其他事(只是在做事的过程中要不断的去问下好了没); 也叫多路IO复用,redis linux中
AIO(异步非阻塞式IO),读取文件时遇到阻塞可以去做别的事,没有阻塞了它会把文件直接发送给你,不需你不断的去轮询的去问
    Netty就是基于AIO的 ;但是linux中不能用,windows可以

NettyRpcEnv是整个通信的环境,RpcEndpoint 终端,可以send和ask消息,还可以receive接收消息,请求和接收消息都要走Dispatcher调度器,它来决定你发送的消息该往收件箱还是发件箱走;在收件箱按照队列方式排列好,周期性的去取FIFO(先进先出),消息处理完之后发往send另外一个RpcEndPoint,往发件箱OubBox里走(会有一个远程地址RpcEndpointAddress-->该给谁发消息);发消息时会有一个TransportClient,往远程去发(通过Netty网络通信去发);

远程TransportServer接收消息又回到了Inbox,处理再返回;  ===>整个过程形成8字环绕

Spark任务调度

RDD是对数据计算逻辑的抽象;真正执行(行动算子)的时候才会计算; 

在DAGScheduler的handleJobSubmitted这个方法中完成了阶段的划分 finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
--->val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite) //结果阶段,作业执行一遍这个对象也是就创建一遍
把当前作业当作整体形成完整的阶段--称作ResultStage
val parents = getOrCreateParentStages(rdd, jobId) //参数rdd是handleJobSubmitted(finalRDD,xxx)给的,即当前要执行行动算子的rdd叫做finalRDD
private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
getShuffleDependencies(rdd).map { shuffleDep =>
//把rdd传进去获取shuffle的依赖关系,.map把得到的结果进行转换;getShuffleDependencies返回的结果是parents val parents = new HashSet[ShuffleDependency[_, _, _]]
case shuffleDep: ShuffleDependency[_, _, _] => parents += shuffleDep
先压栈new Stack[RDD[_]].push(rdd),循环如果不为空就弹出来.pop()
判断依赖关系中有没有shuffle依赖,有就加上 parents += shuffleDep
case dependency => waitingForVisit.push(dependency.rdd)//没有shuffle就把上一层的rdd取出来,再做循环,push、pop不断的取上一级做判断
getOrCreateShuffleMapStage(shuffleDep, firstJobId) //它又new ShuffleMapStage()
}.toList
}
ShuffleMapStage和ResultStage两个阶段,最后构建job、设定、提交submitJob

 spark任务的划分、分配和执行

RDD读取file数据(按一行一行的读取),放到3个分区;发现0号分区不是想要的比如统计wordcount,分解产生了一个新的rdd(所有的转换算子等同于产生了一个新的rdd,rdd是不可变数据集),把一行一行数据变成一个个的,flatMap;转换结构如(a, 1)这样的结构map;reduceBykey打乱重组,产生shuffle过程

有向无环图的调度器,运行并且提交job作业(会进行简单的判断,jobWaiter传到样例类一旦发生特殊事件就产生特殊的方法 handlerJobSubmit完成了整个过程的阶段划分resultStage); 把当前作业当成一个整体,形成了一个完整的阶段称为resultStage;

阶段怎么划分成一个个任务的了?

map->new MapPartitionsRDD -->extends RDD[U](prev)-->def this(@transient oneParent: RDD[_]) =this(oneParent.context, List(new OneToOneDependency(oneParent)))
OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) ,源码中只有窄依赖,不是窄的就是宽的呗就是有shuffle的
创建了两个Stage,给的是finalStage, 创建job,提交submitStage(finalStage)
val missing = getMissingParentStages(stage).sortBy(_.id) //看看有没有上一级的阶段即查找ShuffleMapStage
new HashSet[Stage].push(stage.rdd) 把当前的rdd传到栈里-压栈,---不为空-->弹栈.pop()
-->visit() rdd.dependencies取出它的依赖关系,找到shuffle的依赖关系,val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)有就拿来没有就创建新的
missing += mapStage //放到hashSet里 missing.toList
missing不为空 --> for (parent <- missing) {submitStage(parent)}循环遍历提交每一个Stage
准备提交ResultStage时把它ShuffleMapStage取出来; submitStage(parent)}又自己调自己,找它ShuffleMapStage的上一级,为空-->submitMissingTasks(stage, jobId.get)提交任务
提交的是shuffleStage,找的时候是一层层往上找,提交时是从最开始那一层层往下submitWaitingChildStages(stage)
提交任务:
只有两种类型的Stage
case s: ShuffleMapStage =>
case s: ResultStage =>
taskIdToLocations,最优位置决定计算在哪里算,task和位置进行关联来决定在哪个executor中执行 getPreferredLocs(stage.rdd, p) case stage: ShuffleMapStage => partitionsToCompute.map计算分区 locs part ShuffleMapTask //一个阶段中多个分区map转换一个分区形成一个任务
case stage: ResultStage => p locs part ResultTask
用shuffle来划分stage,因为shuffle是要落盘的;flatmap、map处理数据中间不停留,直接处理往下走,RDD之间每个分区的数据都是连贯的,但是
在shuffle阶段它就不连贯了,在这个阶段它要等待所有的数据都处理完了,shuffle是要全落盘的;这也是划分阶段的原因;
任务跟阶段有关系,有多少个分区就有多少个任务
taskScheduler.submitTasks(new TaskSet()) //任务的调度器它来提交任务,封装成任务集
val manager = createTaskSetManager(taskSet, maxTaskFailures)//任务集管理器
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) //manager会放到任务池中
--->FIFOSchedulableBuilder 可以往里边放任务addTaskSetManager{ rootPool.addSchedulable(manager)} //任务池,TaskPool把任务划分好了来不及执行,可能划分好任务时资源还没申请到,
backend.reviveOffers() --> launchTasks(scheduler.resourceOffers(workOffers))查找任务集
resourceOffers 看看有没有可用的资源executor, 拿到之后 launchTasks()[TaskDescription] ser.serialize(task)把任务序列化,executorData,executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
找到executor向它的终端发送消息--启动task,把得到的任务发送过去;
反向注册目的是启动一个Executor之后告诉Driver我准备好了-->可以发任务了;
启动:
case LaunchTask(data) => if (executor == null) -->val taskDesc = ser.deserialize[TaskDescription](data.value)反序列化得到任务对象
executor.launchTask() -->run() task.run() //只有两种task
以ResultTask为例:
runTask(),shuffle前把数据写到文件里-->rdd.iterator读取数据func(context, rdd.iterator(partition, context))
-->ShuffleRDD中 compute .getReader .read() 读取的是shuffle之前往里写的数据
-->ShuffleMapTask阶段的最后一个RDD runTask() .getWriter 由它来写
taskScheduler.submitTasks(new TaskSet()) //任务的调度器来提交任务把任务封装成一个TaskSet任务集
val manager = createTaskSetManager(taskSet..)//任务集管理器
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) //manager会放到任务池中
-->FIFOSchedulableBuilder 可以往里边放任务addTaskSetManager{ rootPool.addSchedulable(manager)} //任务池
backend.reviveOffers(), launchTasks(scheduler.resourceOffers(workOffers))查找任务集, 拿到之后launchTasks(), ser.serialize(task)把任务序列化
特质SchedulerBackend--实现类--> CoarseGrainedSchedulerBackend它是(driver这边的引用);

Spark内核的更多相关文章

  1. 大数据计算平台Spark内核解读

    1.Spark介绍 Spark是起源于美国加州大学伯克利分校AMPLab的大数据计算平台,在2010年开源,目前是Apache软件基金会的顶级项目.随着 Spark在大数据计算领域的暂露头角,越来越多 ...

  2. (升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)

    本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课 ...

  3. 大数据计算平台Spark内核全面解读

    1.Spark介绍 Spark是起源于美国加州大学伯克利分校AMPLab的大数据计算平台,在2010年开源,目前是Apache软件基金会的顶级项目.随着Spark在大数据计算领域的暂露头角,越来越多的 ...

  4. 【大数据】Spark内核解析

    1. Spark 内核概述 Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制.Spark任务调度机制.Spark内存管理机制.Spark核心功能的运行原理等,熟练掌握Spa ...

  5. Spark内核源码解析

    1.spark内核架构常用术语 Application:基于spark程序,包含一个driver program(客户端程序)和多个executeor(线程) Driver Progrom:代表着sp ...

  6. 【Spark 内核】 Spark 内核解析-上

    Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制.Spark任务调度机制.Spark内存管理机制.Spark核心功能的运行原理等,熟练掌握Spark内核原理,能够帮助我们更 ...

  7. 【Spark 内核】 Spark 内核解析-下

    Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制.Spark任务调度机制.Spark内存管理机制.Spark核心功能的运行原理等,熟练掌握Spark内核原理,能够帮助我们更 ...

  8. Spark内核解析

    Spark内核概述 Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制.Spark任务调度机制.Spark内存管理机制.Spark核心功能的运行原理等,熟练掌握Spark内核 ...

  9. 11、spark内核架构剖析与宽窄依赖

    一.内核剖析 1.内核模块 1.Application 2.spark-submit 3.Driver 4.SparkContext 5.Master 6.Worker 7.Executor 8.Jo ...

  10. [Spark内核] 第28课:Spark天堂之门解密

    本課主題 什么是 Spark 的天堂之门 Spark 天堂之门到底在那里 Spark 天堂之门源码鉴赏 引言 我说的 Spark 天堂之门就是SparkContext,这篇文章会从 SparkCont ...

随机推荐

  1. Tomcat系列(4)——Tomcat 组件及架构详细部分

    核心部分   1. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...

  2. J.U.C-三剑客[semaphore\CyclicBarrier\CountDownLatch]

    一.semaphore信号量,底层也是基于AQS 使用: /** * 可以理解为控制某个资源最多有多少个线程同时执行,(比如洗手间,并行与排队) * 如果满了只能等待直到其它资源释放(可以理解为并发量 ...

  3. Aras前端的一些知识

    top.aras包含了aras前端大部分的API /* * uiShowItem * 打开物体视窗 * parameters: * 1) itemTypeName - may be empty str ...

  4. Java(18) 集合框架

    一.集合框架 Collectoin                   Map List           set              HashMap ArrayList LinkedList ...

  5. Codeforces Round #505 (rated, Div. 1 + Div. 2, based on VK Cup 2018 Final) B. Weakened Common Divis

    题目链接 让你找一个数,使得这个数,可以被每个二元组的两个数中的一个数整除. 先将第一个二元组的两个数质因数分解一下,分解的质数加入set中,然后,对剩下的n-1个二元组进行遍历,每次遍历到的二元组对 ...

  6. SpringMVC+Apache Shiro+JPA(hibernate)案例教学(四)基于Shiro验证用户权限,且给用户授权

    最新项目比较忙,写文章的精力就相对减少了,但看到邮箱里的几个催更,还是厚颜把剩下的文档补上. 一.修改ShiroDbRealm类,实现它的doGetAuthorizationInfo方法 packag ...

  7. jsp网页连接mysql数据库

    1.在mysql数据库中建立数据表 建一张测试表test,插入几条数据: 2.设置JDBC驱动 将 mysql-connector-java-5.0.8-bin.jar 拷到WebContent/WE ...

  8. asp.net webapi 获取报文体的问题

    用这种方法: var data=await Request.Content.ReadAsStringAsync(); 一般都无法获取到内容.原因是内部的流对象已经到了最后面.要获取到里面的需要把流的位 ...

  9. sonar服务搭建

    1.下载安装包,我安装的是6.7.6版本 https://www.sonarqube.org/downloads/ 2.安装前的环境要求 JDK1.8+   数据库,我用的是Mysql5.6版本 3. ...

  10. encode与decode

    import torch from torch import nn import numpy as np import matplotlib.pyplot as plt import torch.ut ...