SparkContext初始化主要就干了三件事:创建TaskScheduler、DAGSCheduler、SparkUI,看看源码中是怎样实现的。
一、TaskScheduler
结合上编blog,TaskScheduler初始化主要涉及三个组件或对象,TaskSchedulerImpl、StandaloneScheduleBackend、SchedulePool。
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 去连接所有的Master,RegisterApplication把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端口。