spark版本: 2.0.0
1.概念
worker是执行任务的真正服务,它可以管理多个executors,并向master汇报任务的执行情况,现在让我们看看它的启动过程。
由于worker启动过程和master启动过程有一定相通之处,所以在阅读本文之前,请先阅读master的启动过程。
2.worker启动
在启动spark时,需要执行脚本sh start-slaves.sh
,其最终是调用org.apache.spark.deploy.worker.Worker
的main方法。
Worker.java
-----------------
def main(argStrings: Array[String]) {
Utils.initDaemon(log)
val conf = new SparkConf
// 启动worker时,必须提供masterurl ,格式:spark://hostname:port
val args = new WorkerArguments(argStrings, conf)
// 启动核心方法
val rpcEnv = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores,
args.memory, args.masters, args.workDir, conf = conf)
rpcEnv.awaitTermination()
}
startRpcEnvAndEndpoint主要作用就是创建worker对象的rpcEnv对象,并注册worker endpoint。
Worker.scala
----------------------
def startRpcEnvAndEndpoint(
host: String,
port: Int,
webUiPort: Int,
cores: Int,
memory: Int,
masterUrls: Array[String],
workDir: String,
workerNumber: Option[Int] = None,
conf: SparkConf = new SparkConf): RpcEnv = {
// The LocalSparkCluster runs multiple local sparkWorkerX RPC Environments
// worker endpoint名称
val systemName = SYSTEM_NAME + workerNumber.map(_.toString).getOrElse("")
// 安全管理器
val securityMgr = new SecurityManager(conf)
// rpcEnv对象创建
val rpcEnv = RpcEnv.create(systemName, host, port, conf, securityMgr)
// 获取有效的master地址
val masterAddresses = masterUrls.map(RpcAddress.fromSparkURL(_))
// 启动并注册worker endpoint
rpcEnv.setupEndpoint(ENDPOINT_NAME, new Worker(rpcEnv, webUiPort, cores, memory,
masterAddresses, ENDPOINT_NAME, workDir, conf, securityMgr))
rpcEnv
}
和master启动一样,在执行setupEndpoint这个方法时会触发endpoint.onStart方法即worker.onStart方法。
Worker.scala
-----------------------
override def onStart() {
// 当前woker
assert(!registered)
logInfo("Starting Spark worker %s:%d with %d cores, %s RAM".format(
host, port, cores, Utils.megabytesToString(memory)))
logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")
logInfo("Spark home: " + sparkHome)
// 创建worker的工作目录,用于保存执行过程的文件
createWorkDir()
// 启动shuffle服务 【1】
shuffleService.startIfEnabled()
webUi = new WorkerWebUI(this, workDir, webUiPort)
webUi.bind()
val scheme = if (webUi.sslOptions.enabled) "https" else "http"
workerWebUiUrl = s"$scheme://$publicAddress:${webUi.boundPort}"
// 注册到master服务 【2】
registerWithMaster()
metricsSystem.registerSource(workerSource)
metricsSystem.start()
// Attach the worker metrics servlet handler to the web ui after the metrics system is started.
metricsSystem.getServletHandlers.foreach(webUi.attachHandler)
}
代码分析:
【1】
最终调用的是
def start() {
require(server == null, "Shuffle server already started")
logInfo(s"Starting shuffle service on port $port with useSasl = $useSasl")
val bootstraps: Seq[TransportServerBootstrap] =
if (useSasl) {
Seq(new SaslServerBootstrap(transportConf, securityManager))
} else {
Nil
}
// 启动一个shuffle服务
server = transportContext.createServer(port, bootstraps.asJava)
}
看到这个transportContext.createServer(port, bootstraps.asJava)
在结合rpc原理这篇,可以知道会创建一个netty server,不过需要注意的是这里的handler对象是ExternalShuffleBlockHandler
,方法比较简单,就不做分析。
【2】注册到master服务是worker启动的核心内容,现在来看一下:先主动触发注册到所有master的任务,如果没有注册成功,就使用定时任务注册(如果存在注册到某一个master,就会停止注册的定时器)
Worker.scala
-----------------------
private def registerWithMaster() {
// onDisconnected may be triggered multiple times, so don't attempt registration
// if there are outstanding registration attempts scheduled.
// 防止有多个注册线程
registrationRetryTimer match {
case None =>
registered = false
// 返回所有的注册到master任务(异步任务列表)
registerMasterFutures = tryRegisterAllMasters()
// 连接重试次数
connectionAttemptCount = 0
// 定时发送注册到master任务(注意:如果有注册成功的就会关闭这个定时器)
registrationRetryTimer = Some(forwordMessageScheduler.scheduleAtFixedRate(
new Runnable {
override def run(): Unit = Utils.tryLogNonFatalError {
Option(self).foreach(_.send(ReregisterWithMaster))
}
},
INITIAL_REGISTRATION_RETRY_INTERVAL_SECONDS,
INITIAL_REGISTRATION_RETRY_INTERVAL_SECONDS,
TimeUnit.SECONDS))
case Some(_) =>
logInfo("Not spawning another attempt to register with the master, since there is an" +
" attempt scheduled already.")
}
}
tryRegisterAllMasters会遍历所有的master列表,尝试一一注册,知道成功为止。
Worker.scala
-----------------------
/**
* spark可以存在多个master,所以每个master都会尝试注册
* @return
*/
private def tryRegisterAllMasters(): Array[JFuture[_]] = {
masterRpcAddresses.map { masterAddress =>
registerMasterThreadPool.submit(new Runnable {
override def run(): Unit = {
try {
logInfo("Connecting to master " + masterAddress + "...")
// 获取master endpoint的引用
val masterEndpoint = rpcEnv.setupEndpointRef(masterAddress, Master.ENDPOINT_NAME)
registerWithMaster(masterEndpoint)
} catch {
case ie: InterruptedException => // Cancelled
case NonFatal(e) => logWarning(s"Failed to connect to master $masterAddress", e)
}
}
})
}
}
上面代码中的setupEndpointRef
有点复杂,这里简单分析一下,最终它会调用方法:
NettyRpcEnv.scala
------------------------------
/**
* 根据url同步获取endpoint引用
* @param uri
* @return
*/
def asyncSetupEndpointRefByURI(uri: String): Future[RpcEndpointRef] = {
val addr = RpcEndpointAddress(uri)
// 构造endpoint引用
val endpointRef = new NettyRpcEndpointRef(conf, addr, this)
// 创建校验器的引用对象,在addr.rpcAddress这台服务器上可以通过RpcEndpointVerifier.NAME找到对应的endpoint
val verifier = new NettyRpcEndpointRef(
conf, RpcEndpointAddress(addr.rpcAddress, RpcEndpointVerifier.NAME), this)
verifier.ask[Boolean](RpcEndpointVerifier.CheckExistence(endpointRef.name)).flatMap { find =>
if (find) {
Future.successful(endpointRef)
} else {
Future.failed(new RpcEndpointNotFoundException(uri))
}
}(ThreadUtils.sameThread)
}
verifier.ask这段代码和master启动的val portsResponse = masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest)
思路差不多,不过它会将请求发送到outbox中,然后处理。不过后面会专门介绍inbox和outbox的整个请求和响应流程,现在只要知道verifier.ask请求master的host和port组成的地址对象的netty server, 找到endpoint名称为RpcEndpointVerifier.NAME
(即校验endpoint,在前面的rpc原理说提到过,在NettyRpcEnv.startServer时就会注册校验endpoint),询问endpointRef.name
(对应的是master的endpoint名称)这个是不是一个有效的endpint名称。
再次回归到tryRegisterAllMasters方法,它会执行方法registerWithMaster(masterEndpoint)
。
/**
* 注册到指定master endpoint引用对象中
* @param masterEndpoint
*/
private def registerWithMaster(masterEndpoint: RpcEndpointRef): Unit = {
// 请求类型: RegisterWorker,响应类型: RegisterWorkerResponse
masterEndpoint.ask[RegisterWorkerResponse](RegisterWorker(
workerId, host, port, self, cores, memory, workerWebUiUrl))
.onComplete {
// This is a very fast action so we can use "ThreadUtils.sameThread"
// 处理成功
case Success(msg) =>
Utils.tryLogNonFatalError {
// 处理master的响应
handleRegisterResponse(msg)
}
// 处理失败,worker进程退出
case Failure(e) =>
logError(s"Cannot register with master: ${masterEndpoint.address}", e)
System.exit(1)
}(ThreadUtils.sameThread)
}
上面的处理过程是:请求服务端master endpoint,请求类型是RegisterWorker(注册Worker),如果响应成功就调用方法handleRegisterResponse。如果注册成功,worker服务将更新master的属性,定期向master发送心跳,周期性清理旧数据等。
Worker.scala
-------------------
private def handleRegisterResponse(msg: RegisterWorkerResponse): Unit = synchronized {
msg match {
// 注册成功
case RegisteredWorker(masterRef, masterWebUiUrl) =>
logInfo("Successfully registered with master " + masterRef.address.toSparkURL)
// 注册成功
registered = true
// 改变master相关属性,关闭其他注册相关服务等
changeMaster(masterRef, masterWebUiUrl)
// 定期发送心跳
forwordMessageScheduler.scheduleAtFixedRate(new Runnable {
override def run(): Unit = Utils.tryLogNonFatalError {
// 发送到自己,通过receive方法处理
self.send(SendHeartbeat)
}
}, 0, HEARTBEAT_MILLIS, TimeUnit.MILLISECONDS)
// 周期性发送清理旧app数据
if (CLEANUP_ENABLED) {
logInfo(
s"Worker cleanup enabled; old application directories will be deleted in: $workDir")
forwordMessageScheduler.scheduleAtFixedRate(new Runnable {
override def run(): Unit = Utils.tryLogNonFatalError {
self.send(WorkDirCleanup)
}
}, CLEANUP_INTERVAL_MILLIS, CLEANUP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
}
// executors信息
val execs = executors.values.map { e =>
new ExecutorDescription(e.appId, e.execId, e.cores, e.state)
}
// 发送最新的状态到master
masterRef.send(WorkerLatestState(workerId, execs.toList, drivers.keys.toSeq))
// 注册失败,需要判断worker是不是已经注册成功了,如果是也不用处理
case RegisterWorkerFailed(message) =>
if (!registered) {
logError("Worker registration failed: " + message)
System.exit(1)
}
// master不可用,不用处理
case MasterInStandby =>
// Ignore. Master not yet ready.
}
}
【说明】:前面我们简单介绍了ask方法,现在客户端请求服务端时又出现了一个新的方法:send。这个方法和ask有什么区别呢?主要是ask需要有返回值(双向请求),而send是一个oneWayMessage,不用接收返回值。