文章目录
- 一、向Master申请启动Driver
- 二、启动Driver
- 三、DriverWrapper向Master申请资源(给Application)
一、向Master申请启动Driver
SparkSubmit的main方法执行,首先要设置一些参数:
//设置参数
val appArgs = new SparkSubmitArguments(args)
接着会进行模式匹配,匹配到submit,调用submit方法,做了两件事:
//以下方法prepareSubmitEnvironment 返回四元组,重点注意childMainClass类 这里以standalone-cluster为例
// childMainClass:未来启动Driver的main class是谁
val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)
//假设standalone模式,集群提交
def doRunMain(): Unit = ......
首先要准备要提交的环境,它会进行一系列的判断:以standalone还是yarn的方式提交,还是以client或者cluster模式等等。以standalone的cluster模式为例,它又会去判断到底是代码还是命令行方式执行。这里会得到一个重要的变量:
childMainClass = REST_CLUSTER_SUBMIT_CLASS
private[deploy] val STANDALONE_CLUSTER_SUBMIT_CLASS = classOf[ClientApp].getName()
可以简单将childMainClass 理解为是ClientApp,继承自SparkApplication,要加载的就是这东西。
准备好要提交的环境后,会执行runMain方法,并将刚才得到的childMainClass 作为参数传过来、加载、实例化为对象。换句话说,就是要加载并实例化ClientApp对象。随后会调用start方法,即ClientApp#start方法。这个start干了两件事:
- 创建RPC通信环境
- 注册Client节点
//创建rpc通信环境
val rpcEnv =
RpcEnv.create("driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))
//得到Master的通信邮箱
val masterEndpoints = driverArgs.masters.map(RpcAddress.fromSparkURL).
map(rpcEnv.setupEndpointRef(_, Master.ENDPOINT_NAME))
//在rpc中设置提交当前任务的Endpoint,只要设置肯定会运行 new ClientEndpoint 类的 start方法
rpcEnv.setupEndpoint("client", new ClientEndpoint(rpcEnv, driverArgs, masterEndpoints, conf))
创建RPC通信环境,跟启动Master、Worker一样,都是创建Netty的RPC环境…
注册这里有点特别,根据以往经验,会塞入"OnStart"作为启动的引信。在onStart方法中,会准备好DriverWrapper类、并将其封装为DriverDescription对象(包含jarUrl、memory、cores等资源):
val mainClass = "org.apache.spark.deploy.worker.DriverWrapper"
val command = new Command(mainClass,
Seq("{{WORKER_URL}}", "{{USER_JAR}}", driverArgs.mainClass) ++ driverArgs.driverOptions,
sys.env, classPathEntries, libraryPathEntries, javaOpts)
val driverDescription = new DriverDescription(
driverArgs.jarUrl,
driverArgs.memory,
driverArgs.cores,
driverArgs.supervise,
command)
对象包装好后,就要向Master发送异步消息来启动Driver,并等待回复:
//向Master申请启动Driver,Master中的 receiveAndReply 方法会接收此请求消息
asyncSendToMasterAndForwardReply[SubmitDriverResponse](
RequestSubmitDriver(driverDescription))
Master的receiveAndReply方法会匹配RequestSubmitDriver,做相应的处理、回复
二、启动Driver
Master首先要判断自己的状态,如果不是alive,就咋滴咋滴。接着会创建Driver节点:
//创建Driver
val driver: DriverInfo = createDriver(description)
private def createDriver(desc: DriverDescription): DriverInfo = {
val now = System.currentTimeMillis()
val date = new Date(now)
new DriverInfo(now, newDriverId(date), desc, date)
}
包装DriverInfo对象,并将Date也塞进去。然后会把刚刚创建好的DriverInfo添加到ArrayBuffer中:
waitingDrivers += driver
private val waitingDrivers = new ArrayBuffer[DriverInfo]
接着就该忙活正事了,调用schedule()申请启动Driver。
首先还是要判断Master状态,如果不是alive就return:
if (state != RecoveryState.ALIVE) {
return
}
换言之,如果Master有问题,是根本没办法启动Driver的。
然后会从HashSet[WorkInfo]过滤出所有alive状态的Worker节点,并打散。目的就是方便后面随机挑选一台Worker节点来启动Driver:
//val workers = new HashSet[WorkerInfo],把所有alive状态的Worker统统过滤出来
//Random.shuffle会把worker打散,方便随机找一台worker节点
val shuffledAliveWorkers = Random.shuffle(workers.toSeq.filter(_.state == WorkerState.ALIVE))
拿着要启动的Driver,和随机挑出来的Worker作比较,看Worker的可用内存、core是否符合Driver的要求。如果满足就去启动Driver。否则就随机取下一个Worker,如果目前所有的Worker都不符合,不会报错,但也不会执行launchDriver方法。它会一直等待下去:
while (numWorkersVisited < numWorkersAlive && !launched) {
//拿到curPos位置的worker
val worker = shuffledAliveWorkers(curPos)
numWorkersVisited += 1
//选中的worker的可用内存,是否大于Driver描述的要求的内存
//还有core
if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) {
//这里是启动Driver,启动Driver之后会为当前的application 申请资源
launchDriver(worker, driver)
waitingDrivers -= driver
launched = true
}
//curPos 就是一直加一的往后取 Worker ,一直找到满足资源的worker
curPos = (curPos + 1) % numWorkersAlive
}
既然要启动Driver,那就得告诉Worker。获取Worker的EndPoint,给Master发消息。Worker的receive方法会接收并匹配到“”LaunchDriver“”(会一并携带Driver的id、desc等。desc里面塞的就是DriverWrapper):
case LaunchDriver(driverId, driverDesc) =>
logInfo(s"Asked to launch driver $driverId")
val driver = new DriverRunner(
conf,
driverId,
workDir,
sparkHome,
driverDesc.copy(command = Worker.maybeUpdateSSLSettings(driverDesc.command, conf)),
self,
workerUri,
securityMgr)
drivers(driverId) = driver
//启动Driver,会初始化 org.apache.spark.deploy.worker.DriverWrapper ,运行main方法
driver.start()
coresUsed += driverDesc.cores
memoryUsed += driverDesc.mem
启动Driver就是启动DriverWrapper,start方法会触发DriverWrapper的main方法的运行。它会启动Driver进程,只要进程有了,就会把我们的代码跑起来。在DriverWrapper中,mainClass就是我们要提交的Application,并从中得到main方法,并执行main方法:
//得到提交application的主方法
val mainMethod = clazz.getMethod("main", classOf[Array[String]])
//启动提交的application 中的main 方法。
mainMethod.invoke(null, extraArgs.toArray[String])
三、DriverWrapper向Master申请资源(给Application)
我们的代码会new SparkContext,它会创建TaskScheduler和DAGScheduler:
private[spark] def taskScheduler: TaskScheduler = _taskScheduler
private[spark] def dagScheduler: DAGScheduler = _dagScheduler
从以下代码可知,Executor的启动内存为1G:
//默认每个Executor启动内存为1024M
_executorMemory = _conf.getOption("spark.executor.memory")
.orElse(Option(System.getenv("SPARK_EXECUTOR_MEMORY")))
.orElse(Option(System.getenv("SPARK_MEM"))
.map(warnSparkMem))
.map(Utils.memoryStringToMb)
.getOrElse(1024)
TaskScheduler和DAGScheduler是一起创建出来的:
val (sched, ts) = SparkContext.createTaskScheduler(this, master, deployMode)
_schedulerBackend = sched //StandaloneSchedulerBackend
_taskScheduler = ts //TaskSchedulerImpl
_dagScheduler = new DAGScheduler(this)
_heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)
createTaskScheduler()方法的参数:master,指的是standalone或者yarn,它返回了1个二元组:
//standalone 提交任务都是以 “spark://”这种方式提交
case SPARK_REGEX(sparkUrl) =>
//scheduler 创建TaskSchedulerImpl 对象
val scheduler = new TaskSchedulerImpl(sc)
val masterUrls = sparkUrl.split(",").map("spark://" + _)
/**
* 这里的 backend 是StandaloneSchedulerBackend 这个类型
*/
val backend = new StandaloneSchedulerBackend(scheduler, sc, masterUrls)
//这里会调用 TaskSchedulerImpl 对象中的 initialize 方法将 backend 初始化,一会要用到
scheduler.initialize(backend)
//返回了 StandaloneSchedulerBackend 和 TaskSchedulerImpl 两个对象
(backend, scheduler)
以standalone为例,SPARK_REGEX代表的就是要匹配“spark://”这样的。可以看出,返回的这个二元组(backend, scheduler),backend就是StandaloneSchedulerBackend,scheduler就是TaskSchedulerImpl对象。得到TaskSchedulerImpl后,会调用start方法,间接走的backend的start方法,最终执行super.start(),也就是走的父类CoarseGrainedSchedulerBackend,创建Driver引用,未来的Executor就是向CoarseGrainedSchedulerBackend中反向注册信息的:
//这就是创建Driver的EndPoint
driverEndpoint = createDriverEndpointRef(properties)
//后面做的事很熟悉了:向Map中添加EndPointData,并将其放到receivers中
protected def createDriverEndpointRef(
properties: ArrayBuffer[(String, String)]): RpcEndpointRef = {
rpcEnv.setupEndpoint(ENDPOINT_NAME, createDriverEndpoint(properties))
}
protected def createDriverEndpoint(properties: Seq[(String, String)]): DriverEndpoint = {
new DriverEndpoint(rpcEnv, properties)
}
ENDPOINT_NAME=“CoarseGrainedScheduler”,这才是真正的Driver,而非DriverWrapper。就是去注册CoarseGrainedScheduler,作为Driver
start之后会将CoarseGrainedExecutorBackend包装成Command对象,并近一步封装为ApplicationDescription对象(里面有启动Executor所需要的的内存、core等信息),最后会将其包装为StandaloneAppClient对象,并调用其start方法:
//往Map里塞AppClient
endpoint.set(rpcEnv.setupEndpoint("AppClient", new ClientEndpoint(rpcEnv)))
只要设置了EndPoint,就一定会调用onStart方法:
override def onStart(): Unit = {
try {
//向Master 注册当前application的信息
registerWithMaster(1)
} catch {
case e: Exception =>
logWarning("Failed to connect to master", e)
markDisconnected()
stop()
}
}
这里会向所有的Master注册Application,以为Master是高可用的。做法就是遍历出所有的Master地址,通过Master的ENDPOINT_NAME获取到Master引用,最后send一个RegisterApplication类型的消息让Master去匹配(携带有command对象、core、memory等)