Spark启动过程中,主要是进行Master和Worker之间的通信。
首先,由Worker节点向Master发送注册信息,然后,Master处理完毕,返回注册成功或者失败消息,如果注册成功,Worker会定时发送心跳给Master。
具体过程如下:
当master节点启动后,随之启动各worker节点,worker启动时会创建RpcEnv,以及EndPoint终端,并向master发送注册worker的消息RegisterWorker。(上一篇文章也提到过,先启动master,再启动worker,因为worker的数量根据实际任务来决定)
(1)worker节点向master节点请求注册
在worker中有一个tryRegisterAllMasters方法,里面会创建一个注册线程池(因为一个worker可能需要注册到多个master中,如HA环境),将注册请求放进线程池中,通过线程池启动注册线程(即并发的进行注册请求)。tryRegisterAllMasters部分源码:
private def tryRegisterAllMasters():Array[JFuture[_]] = { masterRpcAddress.map{ masterAddress => registerMasterThreadPool.submit(new Runnable{ override def run(): Unit = { try{ logInfo("Connecting to master " + masterAddress + "......") //获取master终端引用 val masterEndPoint = rpcEnv.setupEndPointRef(masterAddress, Master.ENDPOINT_NAME) //注册信息 registerWithMaster(masterEndPoint) }catch{......} ...... }
上述源码里面,可以看到是遍历masterRpcAddress,一个worker向每个master都发送一条注册信息请求,但这个请求并不是马上执行,而是放在线程池中。registerWIthMaster方法则是对注册请求的处理,registerWIthMaster方法部分源码:
private def registerWithMaster(masterEndPoint:RpcEndpointRef):Unit = { //根据Master终端引用,发送注册信息 masterEndpoint.ask[RegisterWorkerResponse](RegisterWorker(workerId,host,port,self,cores,memory,workerWebUiUrl)) .onComplete{ //RegisterWorker处理之后会有两种结果,一个是Success,另一个是Failure。根据结果,进行不同的后续处理。 case Success(msg) => Utils.tryLogNonFatalError{handleRegisterResponse(msg)} case Failure(e) => logError(s"Cannot register with master: ${masterEndpoint.address}", e) System.exit(1) //退出 }(ThreadUtils.sameThread) }
(2)Master节点处理处理来自Worker节点的注册请求
master收到消息后,需要对worker发送的信息进行验证、记录。如果注册成功,则发送RegisterWorker消息给对应的worker,worker接收到成功信息,则定期发送心跳信息给master;如果注册失败,则会发送RegisterWorkerFailed消息,Worker打印出错日志并结束Worker启动。上面的RegisterWorker源码如下:
case RegisterWorker(id, workerHost, workerPort, workerRef, cores, memory, workerWebUiUrl) =>{ if(state == RecoveryState.STANDBY){ //如果master处于standby状态,返回“Master处于StandBy状态”信息 context.reply(MasterInStandby) }else if(idWorker.coontains(id)){ //如果列表中已经存在该worker信息,则返回RegisterWorkerFailed信息 context.reply(RegisterWorkerFailed("Duplicate worker ID")) }else{ val worker = new WorkerInfo(id, workerHost, workerPort, cores, memory, workerRef, workerWebUiUrl) //registerWorker(worker)用于将worker放到列表中 if(registerWorker(worker)){ //放入列表成功 persistenceEngine.addWorker(worker) context.reply(RegisteredWorker(self, masterWebUiUrl)) //心跳发送 schedule() }else{ //放入列表失败 val workerAddress = worker.endpoint.address context.reply(RegisterWorkerFailed("Attempted to re-register worker at same address: "+ workerAddress)) } } }
(3)Worker节点注册成功后的心跳通信
当worker节点接收到注册成功信息时,会进行两个操作:1、记录日志并更新master信息;2、向master发送worker的各个executor的最新状态信息,以及定时发送心跳信息。心跳时间可以通过spark.worker.timeout设置,这个是worker“罢工”时间间隔,即判断worker存活的时间,而心跳时间则是该值的1/4。
private val HEARTBEAT_MILLS = conf.getLong("spark.worker.timeout",60) * 1000 / 4
心跳发送RegisteredWorker(self, masterWebUiUrl)的部分源码如下:
case RegisteredWorker(masterRef, masterWebUiUrl) =>{ loginInfo("Successfully registered with master" + masterRef.address.toSparkURL) registered = true changeMaster(masterRef, masterWebUiUrl) //更新worker所持有的master信息 //定时发送的心跳 forwordMessageScheduler.scheduleAtFixedRate(new Runnable{ override def run():Unit = Utils.tryLogNonFataError{ self.send(SendHearbeat) } }, 0, HEARTBEAT_MILLS, TimeUnit.MILLISECONDS) //如果设置清理以前应用使用的文件夹,就进入该逻辑 if(CLEANUP_ENABLED){ ... } //获取worker中各个Executor的最新状态 val execs = executors.values.map{ e=>new ExecutorDescription(e.appId, e.execId, e.cores, e.states=) } //向master汇报worker中各个Executor的最新状态 masterRef.send(WorkerLatesState(workerId, execs.toList, drivers.keys.toSeq)) }
总结:Spark的Worker启动消息通信过程
worker并发启动注册请求线程——>对应的master接收到请求——>master处理请求:若注册失败,返回RegisterMasterFailed提示信息;若注册成功,发送RegisteredMaster提示信息——>worker接收返回信息:若是RegisterMasterFailed信息,则结束线程;若是RegisteredMaster信息,则向master发送executors状态,以及定时发送心跳信息。