SparkContext源码阅读笔记

SparkContext初始化主要就干了三件事:创建TaskScheduler、DAGSCheduler、SparkUI,看看源码中是怎样实现的。

一、TaskScheduler

结合上编blog,TaskScheduler初始化主要涉及三个组件或对象,TaskSchedulerImpl、StandaloneScheduleBackendSchedulePool。

1、从主构造函数中可以看到,调用了createTaskScheduler方法,会返回一个tuple(Return a 2-tuple of the scheduler backend and the task scheduler.)

val (sched, ts) = SparkContext.createTaskScheduler(this, master, deployMode)

2、从createTaskScheduler()方法看出,针对不同的提交模式会创建会有不同的创建方法,这里看些standalone模式

case SPARK_REGEX(sparkUrl) =>
  val scheduler = new TaskSchedulerImpl(sc)
  val masterUrls = sparkUrl.split(",").map("spark://" + _)
  val backend = new StandaloneSchedulerBackend(scheduler, sc, masterUrls)
  scheduler.initialize(backend)
  (backend, scheduler)

3、可以看到第一行代码就是去创建TaskSchedulerImpl

val scheduler = new TaskSchedulerImpl(sc)

     关于TaskSchedulerImpl:1、底层通过操作一个SchedulerBackend,针对不同种类的cluster(standalone、yarn、mesos),调度task
      2、它也可以通过使用一个LocalBackend,并且将isLocal参数设置为true,来在本地模式下工作
      3、它负责处理一些通用的逻辑,比如说决定多个job的调度顺序,启动推测任务执行

      4、客户端首先应用调用它的initialize()方法和start()方法,然后通过runTask()方法提交task sets

4、创建StandaloneSchedulerBackend

val backend = new StandaloneSchedulerBackend(scheduler, sc, masterUrls)

5、调用初始化方法

scheduler.initialize(backend)

可以看到TaskSchedulerImpl的initialize方法主要是创建一个调度池Pool

def initialize(backend: SchedulerBackend) {
  this.backend = backend
  // temporarily set rootPool name to empty
  rootPool = new Pool("", schedulingMode, 0, 0)
  schedulableBuilder = {
    schedulingMode match {
      case SchedulingMode.FIFO =>
        new FIFOSchedulableBuilder(rootPool)
      case SchedulingMode.FAIR =>
        new FairSchedulableBuilder(rootPool, conf)
      case _ =>
        throw new IllegalArgumentException(s"Unsupported spark.scheduler.mode: $schedulingMode")
    }
  }
  schedulableBuilder.buildPools()
}

至此TaskScheduler就创建完了。

二、DAGSCheduler

实现了面向stage的调度机制的高层次的调度层。它会为每个job计算一个stage的DAG(有向无环图),追踪RDD和stage的输出是否被物化了(物化就是,写入了磁盘或者内存等地方),并且寻找一个最少消耗(最优、最小)调度机制来运行job。他会将stage作为tasksets提交到底层的TaskSChedulerImpl上,来在集群上运行它们(task)。

除了处理stage的DAG,它还负责决定运行每个task的最佳位置,基于当前的缓存状态,将这些最佳位置提交给底层的TaskSchedulerImpl。此外,他会处理有Shuffle输出文件丢失导致的失败,在这种情况下,旧的stage可能就会被重庆提交。一个stage内部的失败,如果不是由于Shuffle文件丢失所导致,会被TaskScheduler处理,他会多次重试每一个task,直到超过限制,才会去取消整个stage。

三、启动TaskSchedulerImpl

TaskScheduler在DAGScheduler构造函数中设置关联后,调用TaskSchedulerImpl的start()方法

_taskScheduler.start()

TaskSchedulerImpl的start()方法中主要是调了SchedulerBackend的start方法

backend.start()

SchedulerBackend的start方法就比较关键了

先是做了一些参数的准备,最重要的是构造了ApplicationDescription,应用程序的描述信息,代表当前执行的这个Application的一些情况,包括最大需要多少cpu core,每个slave上需要多少内存等

val appDesc = new ApplicationDescription(sc.appName, maxCores, sc.executorMemory, command,
  appUIAddress, sc.eventLogDir, sc.eventLogCodec, coresPerExecutor, initialExecutorLimit)

接下来创建了一个StandaloneAppClient

client = new StandaloneAppClient(sc.env.rpcEnv, masters, appDesc, this, conf)

它负责为Application与spark集群通信。

它会接收一个spark master的url、以及一个ApplicationDescription、和一个集群事件的监听器、以及各种事件发生时的监听器回调函数

调用appclient的start()方法

client.start()

这个start方法,创建启动了一个rpcEndpoint(2.x基于Netty的rpc框架1.x是基于Akka,就是创建一个Actor,可以理解为启动一个线程)异步消息框架

def start() {
  // Just launch an rpcEndpoint; it will call back into the listener.
  endpoint.set(rpcEnv.setupEndpoint("AppClient", new ClientEndpoint(rpcEnv)))
}

这个ClientEndpoint其实是StandaloneAppClient的内部类,继承了ThreadSafeRpcEndpoint和混入Logging

一个RpcEndpoint经历的过程依次是:构建->onStart→receive→onStop。

override def onStart(): Unit = {
  try {
    registerWithMaster(1)
  } catch {
    case e: Exception =>
      logWarning("Failed to connect to master", e)
      markDisconnected()
      stop()
  }
}

可以看到onStart中调用了registerWithMaster方法,又调用tryRegisterAllMasters 去连接所有的MasterRegisterApplication把appDescription发送给Master,Master就会去注册Application

private def registerWithMaster(nthRetry: Int) {
  registerMasterFutures.set(tryRegisterAllMasters())
private def tryRegisterAllMasters(): Array[JFuture[_]] = {
  for (masterAddress <- masterRpcAddresses) yield {
    registerMasterThreadPool.submit(new Runnable {
      override def run(): Unit = try {
        if (registered.get) {
          return
        }
        logInfo("Connecting to master " + masterAddress.toSparkURL + "...")
        val masterRef = rpcEnv.setupEndpointRef(masterAddress, Master.ENDPOINT_NAME)
        masterRef.send(RegisterApplication(appDescription, self))
      } catch {
        case ie: InterruptedException => // Cancelled
        case NonFatal(e) => logWarning(s"Failed to connect to master $masterAddress", e)
      }
    })
  }
}

四、SparkUI

Some(SparkUI.createLiveUI(this, _conf, listenerBus, _jobProgressListener,
  _env.securityManager, appName, startTime = startTime))
创建一个Jetty服务器,绑定在4040端口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值