spark的提交过程:
用户在客户端提交,客户端机器会找到master要求开启driver,master在他的worker节点找到符合条件的机器然后最终在这个worker启动driver。
代码分析如下:
入口类: sparksubmit def main();
def main(args: Array[String]): Unit = {
val submit = new SparkSubmit()
submit.doSubmit(args)
}
跳到doSubmit,执行submit
def doSubmit(args: Array[String]): Unit = {
// Initialize logging if it hasn't been done yet. Keep track of whether logging needs to
// be reset before the application starts.
// 初始化log4j
val uninitLog = initializeLogIfNecessary(true, silent = true)
// 初始化参数 这个aggArg会最终带入到master->driver
val appArgs = parseArguments(args)
if (appArgs.verbose) {
logInfo(appArgs.toString)
}
appArgs.action match {
// 最终提交
case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
case SparkSubmitAction.PRINT_VERSION => printVersion()
}
}
submit最终走到runMain:
private def runMain(
childArgs: Seq[String],
childClasspath: Seq[String],
sparkConf: SparkConf,
childMainClass: String,
verbose: Boolean): Unit = {
if (verbose) {
logInfo(s"Main class:\n$childMainClass")
logInfo(s"Arguments:\n${childArgs.mkString("\n")}")
// sysProps may contain sensitive information, so redact before printing
logInfo(s"Spark config:\n${Utils.redact(sparkConf.getAll.toMap).mkString("\n")}")
logInfo(s"Classpath elements:\n${childClasspath.mkString("\n")}")
logInfo("\n")
}
val loader =
if (sparkConf.get(DRIVER_USER_CLASS_PATH_FIRST)) {
new ChildFirstURLClassLoader(new Array[URL](0),
Thread.currentThread.getContextClassLoader)
} else {
new MutableURLClassLoader(new Array[URL](0),
Thread.currentThread.getContextClassLoader)
}
Thread.currentThread.setContextClassLoader(loader)
for (jar <- childClasspath) {
addJarToClasspath(jar, loader)
}
var mainClass: Class[_] = null
try {
// 远程代理对象拿到入口类
mainClass = Utils.classForName(childMainClass)
} catch {
case e: ClassNotFoundException =>
logWarning(s"Failed to load $childMainClass.", e)
if (childMainClass.contains("thriftserver")) {
logInfo(s"Failed to load main class $childMainClass.")
logInfo("You need to build Spark with -Phive and -Phive-thriftserver.")
}
throw new SparkUserAppException(CLASS_NOT_FOUND_EXIT_STATUS)
case e: NoClassDefFoundError =>
logWarning(s"Failed to load $childMainClass: ${e.getMessage()}")
if (e.getMessage.contains("org/apache/hadoop/hive")) {
logInfo(s"Failed to load hive class.")
logInfo("You need to build Spark with -Phive and -Phive-thriftserver.")
}
throw new SparkUserAppException(CLASS_NOT_FOUND_EXIT_STATUS)
}
//这里没大看懂,但好像是根据mainClass来创建一个SparkApplication
// RestSubmissionClientApp 和 ClientApp均是SparkApplication 的子类
// 所以创建出来的app对象应该是 RestSubmissionClientApp 或者 ClientApp
//这样,后面调用的start()方法才能够 调用子类中重写的start()方法 进行driver的注册
val app: SparkApplication = if (classOf[SparkApplication].isAssignableFrom(mainClass)) {
mainClass.newInstance().asInstanceOf[SparkApplication]
} else {
// SPARK-4170
if (classOf[scala.App].isAssignableFrom(mainClass)) {
logWarning("Subclasses of scala.App may not work correctly. Use a main() method instead.")
}
new JavaMainApplication(mainClass)
}
@tailrec
def findCause(t: Throwable): Throwable = t match {
case e: UndeclaredThrowableException =>
if (e.getCause() != null) findCause(e.getCause()) else e
case e: InvocationTargetException =>
if (e.getCause() != null) findCause(e.getCause()) else e
case e: Throwable =>
e
}
// 开启, 调用子类中重写的start()方法 进行driver的注册
try {
app.start(childArgs.toArray, sparkConf)
} catch {
case t: Throwable =>
throw findCause(t)
}
}
之后就是clientapp里的start了,1.创建clientendpoint,2、获取master endpointrefyinyong。然后在创建过程中发送指令给master
override def start(args: Array[String], conf: SparkConf): Unit = {
// driverargs中的cmd重合而来
val driverArgs = new ClientArguments(args)
if (!conf.contains("spark.rpc.askTimeout")) {
conf.set("spark.rpc.askTimeout", "10s")
}
Logger.getRootLogger.setLevel(driverArgs.logLevel)
// 创建rpc
val rpcEnv =
RpcEnv.create("driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))
val masterEndpoints = driverArgs.masters.map(RpcAddress.fromSparkURL).
map(rpcEnv.setupEndpointRef(_, Master.ENDPOINT_NAME))
// 创建driver的rpcendpoing
rpcEnv.setupEndpoint("client", new ClientEndpoint(rpcEnv, driverArgs, masterEndpoints, conf))
rpcEnv.awaitTermination()
}
看下ClientEndpoint 的启动,最重要的就是发送启动driver命令给master
override def onStart(): Unit = {
driverArgs.cmd match {
case "launch" =>
// TODO: We could add an env variable here and intercept it in `sc.addJar` that would
// truncate filesystem paths similar to what YARN does. For now, we just require
// people call `addJar` assuming the jar is in the same directory.
val mainClass = "org.apache.spark.deploy.worker.DriverWrapper"
val classPathConf = "spark.driver.extraClassPath"
val classPathEntries = sys.props.get(classPathConf).toSeq.flatMap { cp =>
cp.split(java.io.File.pathSeparator)
}
val libraryPathConf = "spark.driver.extraLibraryPath"
val libraryPathEntries = sys.props.get(libraryPathConf).toSeq.flatMap { cp =>
cp.split(java.io.File.pathSeparator)
}
val extraJavaOptsConf = "spark.driver.extraJavaOptions"
val extraJavaOpts = sys.props.get(extraJavaOptsConf)
.map(Utils.splitCommandString).getOrElse(Seq.empty)
val sparkJavaOpts = Utils.sparkJavaOpts(conf)
val javaOpts = sparkJavaOpts ++ extraJavaOpts
val command = new Command(mainClass,
Seq("{{WORKER_URL}}", "{{USER_JAR}}", driverArgs.mainClass) ++ driverArgs.driverOptions,
sys.env, classPathEntries, libraryPathEntries, javaOpts)
// driver启动信息,最终通过master传给worker
val driverDescription = new DriverDescription(
driverArgs.jarUrl,
driverArgs.memory,
driverArgs.cores,
driverArgs.supervise,
command)
// 向master发送启动driver命令 RequestSubmitDriver 对应到master类的 receiveAndReply
asyncSendToMasterAndForwardReply[SubmitDriverResponse](
RequestSubmitDriver(driverDescription))
case "kill" =>
val driverId = driverArgs.driverId
asyncSendToMasterAndForwardReply[KillDriverResponse](RequestKillDriver(driverId))
}
}
然后看下MASTER收到命令的运行,
override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
case RequestSubmitDriver(description) =>
if (state != RecoveryState.ALIVE) {
val msg = s"${Utils.BACKUP_STANDALONE_MASTER_PREFIX}: $state. " +
"Can only accept driver submissions in ALIVE state."
context.reply(SubmitDriverResponse(self, false, None, msg))
} else {
logInfo("Driver submitted " + description.command.mainClass)
// 创建driver
val driver = createDriver(description)
persistenceEngine.addDriver(driver)
waitingDrivers += driver
drivers.add(driver)
schedule()
// TODO: It might be good to instead have the submission client poll the master to determine
// the current status of the driver. For now it's simply "fire and forget".
context.reply(SubmitDriverResponse(self, true, Some(driver.id),
s"Driver successfully submitted as ${driver.id}"))
}
scheduler方法:
private def schedule(): Unit = {
// 首先判断当前 master 的状态是否为 alive 的,如果不是 alive 则不往下执行
if (state != RecoveryState.ALIVE) {
return
}
// Drivers take strict precedence over executors
// 这里主要是将集群中 state 为 alive 的 worker 帅选出来,然后随机打乱
val shuffledAliveWorkers = Random.shuffle(workers.toSeq.filter(_.state == WorkerState.ALIVE))
// 当前 alive 的 worker 数量
val numWorkersAlive = shuffledAliveWorkers.size
var curPos = 0
// 将等待分配资源的 driver 队列中的所有 driver 进行遍历
// 然后为每个 driver 遍历一遍所有的 alive worker,当碰到 worker 的可用内存和比当前队列中
// 等待的 driver 所需要的内存要大并且 worker 的 core 数量也满足 driver 的需求时
// 就会调用 launcherDriver 方法去将 driver 发送对应的 worker 上去执行
for (driver <- waitingDrivers.toList) { // iterate over a copy of waitingDrivers
// We assign workers to each waiting driver in a round-robin fashion. For each driver, we
// start from the last worker that was assigned a driver, and continue onwards until we have
// explored all alive workers.
var launched = false
var numWorkersVisited = 0
while (numWorkersVisited < numWorkersAlive && !launched) {
val worker = shuffledAliveWorkers(curPos)
numWorkersVisited += 1
// 找到符合条件的 worker
if (worker.memoryFree >= driver.desc.mem && worker.coresFree >= driver.desc.cores) {
launchDriver(worker, driver)
// 将该 driver 从等待队列中移除
waitingDrivers -= driver
// 标记当前 driver 为 launched
launched = true
}
// 移到下一个 driver 上
curPos = (curPos + 1) % numWorkersAlive
}
}
// 调用 startExecutorsOnWorkers 方法
startExecutorsOnWorkers()
}
launchDriver方法:
private def launchDriver(worker: WorkerInfo, driver: DriverInfo) {
logInfo("Launching driver " + driver.id + " on worker " + worker.id)
// 这里是将 workerInfo 中添加上启动 driver 的信息,内部也会减去 driver 使用掉的资源
worker.addDriver(driver)
// 将 driver 启动的 worker 信息记录到 driver 中
driver.worker = Some(worker)
// 给 worker 发送 LaunchDriver 的信息
worker.endpoint.send(LaunchDriver(driver.id, driver.desc))
// 标记当前 driver 状态为 running 状态
driver.state = DriverState.RUNNING
}
worker.endpoint.send(LaunchDriver(driver.id, driver.desc)) 发送命令给worker类
// 启动driver
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)
// 然后将这个 runner 保存到一个 HashMap 中
drivers(driverId) = driver
// 启动这个 runner
driver.start()
// 更新当前 worker 的资源信息
coresUsed += driverDesc.cores
memoryUsed += driverDesc.mem
/** Starts a thread to run and manage the driver. */
private[worker] def start() = {
// 开一个线程
new Thread("DriverRunner for " + driverId) {
override def run() {
var shutdownHook: AnyRef = null
try {
shutdownHook = ShutdownHookManager.addShutdownHook { () =>
logInfo(s"Worker shutting down, killing driver $driverId")
kill()
}
// prepare driver jars and run driver
// 准备 driver 的 jar 包并且执行 driver,并返回一个 exitCode
val exitCode = prepareAndRunDriver()
// set final state depending on if forcibly killed and process exit code
// 根据 exitCode 设置 finalState,一共有三种,分别为:FINISHED,KILLED,FAILED
finalState = if (exitCode == 0) {
Some(DriverState.FINISHED)
} else if (killed) {
Some(DriverState.KILLED)
} else {
Some(DriverState.FAILED)
}
} catch {
case e: Exception =>
kill()
finalState = Some(DriverState.ERROR)
finalException = Some(e)
} finally {
if (shutdownHook != null) {
ShutdownHookManager.removeShutdownHook(shutdownHook)
}
}
// notify worker of final driver state, possible exception
// 然后将 driverId 和 driver 执行结果 finalState 以及一些异常信息发送给 worker
worker.send(DriverStateChanged(driverId, finalState.get, finalException))
}
}.start()
}
然后到prepareAndRunDriver:
private[worker] def prepareAndRunDriver(): Int = {
// 创建 driver 的工作目录
val driverDir = createWorkingDirectory()
// 下载 driver 的 jar 包到工作目录下
val localJarFilename = downloadUserJar(driverDir)
def substituteVariables(argument: String): String = argument match {
case "{{WORKER_URL}}" => workerUrl
case "{{USER_JAR}}" => localJarFilename
case other => other
}
// TODO: If we add ability to submit multiple jars they should also be added here
// 创建 ProcessBuilder
val builder = CommandUtils.buildProcessBuilder(driverDesc.command, securityManager,
driverDesc.mem, sparkHome.getAbsolutePath, substituteVariables)
runDriver(builder, driverDir, driverDesc.supervise)
}
再到rundriver:
private def runDriver(builder: ProcessBuilder, baseDir: File, supervise: Boolean): Int = {
builder.directory(baseDir)
// 初始化操作
def initialize(process: Process): Unit = {
// Redirect stdout and stderr to files
// 创建 stout 文件
val stdout = new File(baseDir, "stdout")
// 将 process 的 InputStream 流重定向为 stout 文件
CommandUtils.redirectStream(process.getInputStream, stdout)
// 创建 stderr 文件
val stderr = new File(baseDir, "stderr")
// 将 builder 命令格式化处理
val formattedCommand = builder.command.asScala.mkString("\"", "\" \"", "\"")
val header = "Launch Command: %s\n%s\n\n".format(formattedCommand, "=" * 40)
Files.append(header, stderr, StandardCharsets.UTF_8)
CommandUtils.redirectStream(process.getErrorStream, stderr)
}
// 调用 runCommandWithRetry
runCommandWithRetry(ProcessBuilderLike(builder), initialize, supervise)
}
最后启动command:
private[worker] def runCommandWithRetry(
command: ProcessBuilderLike, initialize: Process => Unit, supervise: Boolean): Int = {
// 退出码
var exitCode = -1
// Time to wait between submission retries.
// 提交重试的等待时间
var waitSeconds = 1
// A run of this many seconds resets the exponential back-off.
val successfulRunDuration = 5
var keepTrying = !killed
while (keepTrying) {
logInfo("Launch Command: " + command.command.mkString("\"", "\" \"", "\""))
synchronized {
// 如果被 kill 则返回 exitcode
if (killed) { return exitCode }
// 执行 command 命令,启动 driver 进程在这里drive进行启动
process = Some(command.start())
// 调用上面定义好的 initialize 方法,将一些流的输出文件做重定向
initialize(process.get)
}
val processStart = clock.getTimeMillis()
exitCode = process.get.waitFor()
// check if attempting another run
keepTrying = supervise && exitCode != 0 && !killed
if (keepTrying) {
if (clock.getTimeMillis() - processStart > successfulRunDuration * 1000L) {
waitSeconds = 1
}
logInfo(s"Command exited with status $exitCode, re-launching after $waitSeconds s.")
sleeper.sleep(waitSeconds)
waitSeconds = waitSeconds * 2 // exponential back-off
}
}
exitCode
}
这样driver启动了,中worker会发送状态变更大master
worker.send(DriverStateChanged(driverId, finalState.get, finalException))
sendToMaster(driverStateChanged)
state match {
case DriverState.ERROR | DriverState.FINISHED | DriverState.KILLED | DriverState.FAILED =>
removeDriver(driverId, state, exception)
case _ =>
throw new Exception(s"Received unexpected state update for driver $driverId: $state")
}