Spark学习-2.4.0-源码分析-2-Spark 核心篇-Master主备切换机制


1. “主备”?


 对于Spark来说,Master实际上可以配置多个,分别为Active Master 和 StandBy Master。而Spark原生的standalone模式是支持Master主备切换的,也就是说,当Active Master节点挂掉的时候,我们可以将Standby Master切换为Active Master。

2. 主备切换机制


  Spark Master主备切换可以基于两种机制

  • 一种是基于文件系统
      基于文件系统的主备切换机制需要在Active Master挂掉的时候,由我们手动去切换到Standby Master上
  • 一种是基于ZooKeeper
      基于ZooKeeper的主备切换机制,当Active Master挂掉之后,通过选举机制,重新选出一个Active节点作为Master

3. 主备切换流程分析

3.1 基于ZooKeeper的切换机制流程

3.1.1 Standby Master成为Active Master

选举阶段:

  1. 对于standby Master来说,成为Active Master 也就意味着他在ZooKeeper中被选举成为了Leader。

Master启动切换阶段:

  1. 首先,前面的masterInstance.electedLeader()方法其实是让Standby Master给自己发了一个ElectedLeader消息,告诉自己已经成为了Leader
  2. 接着,使用PersistenceEngine去读取按其各自的id排序的持久化的storedApps, storedDrivers, storedWorkers
  3. 判断,如果storedApps, storedDrivers, storedWorkers中有任何一个非空的,将持久化的Application、Driver、Worker 的信息,重新进行注册,注册到Master内部的内存缓存结构中

Master与Application通信阶段:

  1. 然后,将Application、Worker的状态都修改为UNKNOWN然后向Application,以及Worker发送一个MasterChanged()的消息,告知他们Active Master已经改变了,地址是当前这个Master的地址
  2. 理论上Application和Worker,理论上来说,如果它们目前都是正常在运行的话,那么在接收到Master发送来的地址之后,就会做出反应并返回响应消息给新Master

Master完成切换阶段:

  1. 此时,Master在陆续接收到Driver和Worker发送来的响应消息之后,会使用completeRecovery()方法对没有发送响应消息的Driver和Worker进行处理过滤掉它们的信息。(1.内存缓存结构中移除 2.从相关组件的内存缓存中移除 3.从持久化存储中移除)
  2. 最后,将自己的状态修改为ALIVE,再调用Master自己的schedule()方法,对正在等待资源调度的Driver和Application进行调度。比如在某个Worker上启动Driver,或者为Application在Worker上启动它需要的Executor。

基于Zookeeper的Master切换流程分析

基于Zookeeper的Master切换流程分析

图片资源地址: 基于Zookeeperr的Master切换流程分析

  

3.1.1.1 在ZooKeeper端中被选举

  ZooKeeper架构:
在这里插入图片描述
  
  选举完成后,ZooKeeperLeaderElectionAgent.scala中会调用Standby Master的electedLeader()方法

  private def updateLeadershipStatus(isLeader: Boolean) {
    if (isLeader && status == LeadershipStatus.NOT_LEADER) {
      //如果这个masterInstance之前不是Leader,而现在已经选举为Leader了,告知他
      status = LeadershipStatus.LEADER
      masterInstance.electedLeader()
    } else if (!isLeader && status == LeadershipStatus.LEADER) {
      //如果这个masterInstance之前是Leader,而先在重新选举后,已经不是了,也告知他
      status = LeadershipStatus.NOT_LEADER
      masterInstance.revokedLeadership()
    }
  }


3.1.1.2 Standby Master接收Zookeeper消息并启动切换

主要代码如下:

  /**
   *  包名:org.apache.spark.deploy.master
   *  类名:Master
   */
 -------------------------------------------------------
  override def electedLeader() {
    self.send(ElectedLeader)
  }
  ---------------------------------------------------------------
  override def receive: PartialFunction[Any, Unit] = {
  	.......
    case ElectedLeader =>
    	//获取按其各自的id排序的持久数据(这意味着它们按创建时间排序)
      val (storedApps, storedDrivers, storedWorkers) = persistenceEngine.readPersistedData(rpcEnv)
      state = if (storedApps.isEmpty && storedDrivers.isEmpty && storedWorkers.isEmpty) {
        RecoveryState.ALIVE
      } else {
        RecoveryState.RECOVERING
      }
      logInfo("I have been elected leader! New state: " + state)
      if (state == RecoveryState.RECOVERING) {
        beginRecovery(storedApps, storedDrivers, storedWorkers)
        recoveryCompletionTask = forwardMessageThread.schedule(new Runnable {
          override def run(): Unit = Utils.tryLogNonFatalError {
            self.send(CompleteRecovery)
          }
        }, WORKER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
      }

    case CompleteRecovery => completeRecovery()
    ......
 }
-------------------------------------------------------------
private def beginRecovery(storedApps: Seq[ApplicationInfo], storedDrivers: Seq[DriverInfo],
    storedWorkers: Seq[WorkerInfo]) {
  // 遍历每一个存储的application,注册该application,并且发送MasterChanged请求
  for (app <- storedApps) {
    logInfo("Trying to recover app: " + app.id)
    try {
      registerApplication(app)
      // 将该application状态置为UNKNOWN状态
      app.state = ApplicationState.UNKNOWN
      // 然后叫这个App所在的Driver向App发送MasterChanged消息
      // 如果APP能够收到这个消息,就会将自身状态改为“未失联”,然后向Master发送确认消息MasterChangeAcknowledged
      // Master接收到这个消息就知道了,此App已经完成了重新注册。
      app.driver.send(MasterChanged(self, masterWebUiUrl))
    } catch {
      case e: Exception => logInfo("App " + app.id + " had exception on reconnect")
    }
  }
  // 遍历每一个存储的driver, 更新master所维护的driver集合
  for (driver <- storedDrivers) {
    drivers += driver
  }
  // 遍历每一个存储的wroker,然后向master注册worker
  for (worker <- storedWorkers) {
    logInfo("Trying to recover worker: " + worker.id)
    try {
      // 注册worker,就是更新master的woker集合,和worker相关的映射列表
      registerWorker(worker)
      // 将该worker状态置为UNKNOWN状态
      worker.state = WorkerState.UNKNOWN
      // 然后改worker向master发送MasterChanged请求
      worker.endpoint.send(MasterChanged(self, masterWebUiUrl))
    } catch {
      case e: Exception => logInfo("Worker " + worker.id + " had exception on reconnect")
    }
  }
}
3.1.1.3 Standby Master与Application、Worker通信,重新注册

上面可以看到,Worker和AppClient会接受到来自Master的MasterChanged消息,

Worker在收到MasterChanged消息后 : worker.endpoint.send(MasterChanged(self, masterWebUiUrl))

  • 获取新的Master的url和master,连接状态置为true,取消之前的尝试重新注册
  • 向新的Master发送WorkerSchedulerStateResponse消息
  • Master接收到消息后会修改信息,启动服务,完成worker的重新注册。然后Master把之前相关的应用程序在worker上进行恢复
------------------------------------Worker-receive()--------------------------
case MasterChanged(masterRef, masterWebUiUrl) =>
  logInfo("Master has changed, new master is at " + masterRef.address.toSparkURL)
  // 获取新的master的url和master,连接状态置为true,取消之前的尝试重新注册
  changeMaster(masterRef, masterWebUiUrl)
  // 创建当前节点executors的简单描述对象ExecutorDescription
  val execs = executors.values.
    map(e => new ExecutorDescription(e.appId, e.execId, e.cores, e.state))
  // 向新的master发送WorkerSchedulerStateResponse消息,然后会做一些操作
  masterRef.send(WorkerSchedulerStateResponse(workerId, execs.toList, drivers.keys.toSeq))

-------------------------------------Master-receive()-------------------------
case WorkerSchedulerStateResponse(workerId, executors, driverIds) =>
  // 根据workerId获取worker
  idToWorker.get(workerId) match {
    case Some(worker) =>
      logInfo("Worker has been re-registered: " + workerId)
      // worker状态置为alive
      worker.state = WorkerState.ALIVE
      // 从指定的executor中过滤出哪些是有效的executor
      val validExecutors = executors.filter(exec => idToApp.get(exec.appId).isDefined)
      // 遍历有效的executors
      for (exec <- validExecutors) {
        // 获取executor所对应的app
        val app = idToApp.get(exec.appId).get
        // 为app设置executor,比如哪一个worker,多少核数等资源
        val execInfo = app.addExecutor(worker, exec.cores, Some(exec.execId))
        // 将该executor添加到该woker上
        worker.addExecutor(execInfo)
        execInfo.copyState(exec)
      }
      // 将所有的driver设置为RUNNING然后加入到worker中
      for (driverId <- driverIds) {
        drivers.find(_.id == driverId).foreach { driver =>
          driver.worker = Some(worker)
          driver.state = DriverState.RUNNING
          worker.drivers(driverId) = driver
        }
      }
    case None =>
      logWarning("Scheduler state from unknown worker: " + workerId)
  }
  // 判断当前是否可以进行completeRecovery操作,如果可以进行completeRecovery操作
  if (canCompleteRecovery) { completeRecovery() }

AppClient在收到App所在的Driver向App发送MasterChanged后:app.driver.send(MasterChanged(self, masterWebUiUrl))

  • 更新master,将自身状态改为“未失联”
  • 向新的master发送MasterChangeAcknowledged消息
  • Master接收到这个消息,更新application状态为WAITTING
------------------------Application-receive()--------------------------------
case MasterChanged(masterRef, masterWebUiUrl) =>
  logInfo("Master has changed, new master is at " + masterRef.address.toSparkURL)
  // 更新master
  master = Some(masterRef)
  alreadyDisconnected = false
  // 向新的master发送MasterChangeAcknowledged消息
  masterRef.send(MasterChangeAcknowledged(appId.get))
-----------------------Master-receive()---------------------------------------
case MasterChangeAcknowledged(appId) =>
  idToApp.get(appId) match {
    case Some(app) =>
      logInfo("Application has been re-registered: " + appId)
      app.state = ApplicationState.WAITING
    case None =>
      logWarning("Master change ack from unknown app: " + appId)
  }
    // 判断当前是否可以进行completeRecovery操作,如果可以进行completeRecovery操作
  if (canCompleteRecovery) { completeRecovery() }
3.1.1.3 Standby Master完成切换阶段

  从3.1.1.2 可知,在beginRecovery()阶段完成之后,Master会给自己发送一个CompleteRecovery消息,去执行completeRecovery()。关于清理的细节,可以参见文章Master清理信息–Driver、Worker、Application

  ------------------------------------------------------

    private def completeRecovery() {
    // Ensure "only-once" recovery semantics using a short synchronization period.
    if (state != RecoveryState.RECOVERING) { return }
    state = RecoveryState.COMPLETING_RECOVERY

  // 过滤掉状态为UNKNOWN的worker和application  (也就数他们没有发送响应消息,切换后状态就一直是UNKNOWN)
  // (清理机制:1.内存缓存结构中移除  2.从相关组件的内存缓存中移除  3.从持久化存储中移除)
  
    // 杀死所有未发送响应消息的Applications
    workers.filter(_.state == WorkerState.UNKNOWN).foreach(
      removeWorker(_, "Not responding for recovery"))
    apps.filter(_.state == ApplicationState.UNKNOWN).foreach(finishApplication)

    // 将复原的Application状态更新为RUNNING
    apps.filter(_.state == ApplicationState.WAITING).foreach(_.state = ApplicationState.RUNNING)

    // Reschedule drivers which were not claimed by any workers
    // 对未被任何worker所认领的Driver,重新调度
    drivers.filter(_.worker.isEmpty).foreach { d =>
      logWarning(s"Driver ${d.id} was not found after master recovery")
      if (d.desc.supervise) {
        logWarning(s"Re-launching ${d.id}")
        relaunchDriver(d)
      } else {
        removeDriver(d.id, DriverState.ERROR, None)
        logWarning(s"Did not re-launch ${d.id} because it was not supervised")
      }
    }
    state = RecoveryState.ALIVE
    schedule()
    logInfo("Recovery complete - resuming operations!")
  }


3.1.2 Active Master不再Active

  从3.1.1.1中可以知道,Standby Master会接收到来自ZooKeeperLeaderElectionAgent的消息以进行状态切换,而同样Active Master同样会收到它的消息来进行状态改变:
  
  选举完成后,ZooKeeperLeaderElectionAgent.scala中会调用Active Master的revokedLeadership()方法

  private def updateLeadershipStatus(isLeader: Boolean) {
    if (isLeader && status == LeadershipStatus.NOT_LEADER) {
      //如果这个masterInstance之前不是Leader,而现在已经选举为Leader了,告知他
      status = LeadershipStatus.LEADER
      masterInstance.electedLeader()
    } else if (!isLeader && status == LeadershipStatus.LEADER) {
      //如果这个masterInstance之前是Leader,而先在重新选举后,已经不是了,也告知他
      status = LeadershipStatus.NOT_LEADER
      masterInstance.revokedLeadership()
    }
  }

那么流程如下:

  • 首先,masterInstance.revokedLeadership()实际上是Active Master给自己发送一个RevokedLeadership消息,告诉自己不再是Leader
  • 它自己receive()到这个消息之后,就会打印出Leadership has been revoked -- master shutting down.,然后准备退出System.exit(0)
  • 那么既然要退出,那么肯定要先停止相关的组件和服务,结束对资源的占用。这时候,它会去自动执行onStop()方法(就像开始时会执行onStart()方法一样)。在onStop()中,就会释放各种资源,停止各种服务,并且通知其他组件(如masterMetricsSystem),去修改参数等

代码如下:

---------------------------------------------------------------

  override def revokedLeadership() {
    self.send(RevokedLeadership)
  }
---------------------------------------------------------------------
 override def receive: PartialFunction[Any, Unit] = {
    ...
    case RevokedLeadership =>
      logError("Leadership has been revoked -- master shutting down.")
      System.exit(0)
    ...   
}
----------------------------------------------------------------------
  override def onStop() {
    masterMetricsSystem.report()
    applicationMetricsSystem.report()
    // prevent the CompleteRecovery message sending to restarted master
    if (recoveryCompletionTask != null) {
      recoveryCompletionTask.cancel(true)
    }
    if (checkForWorkerTimeOutTask != null) {
      checkForWorkerTimeOutTask.cancel(true)
    }
    forwardMessageThread.shutdownNow()
    webUi.stop()
    restServer.foreach(_.stop())
    masterMetricsSystem.stop()
    applicationMetricsSystem.stop()
    persistenceEngine.close()
    leaderElectionAgent.stop()
  }
  --------------------------------------------------------

3.2 基于文件系统切换机制流程

  大致与前面相同,只是不用选举,而是手动切换的。
官方链接
ZooKeeper是获得生产级别高可用性的最佳方式,但如果您只是希望能够在Master关闭时重新启动Master,则FILESYSTEM模式可以处理它。当应用程序和工作人员注册时,他们有足够的状态写入提供的目录,以便在重新启动主进程时可以恢复它们。

要启用此恢复模式,您可以使用以下配置在spark-env中设置SPARK_DAEMON_JAVA_OPTS:

系统属性含义
spark.deploy.recoveryMode设置为FILESYSTEM以启用单节点恢复模式(默认值:NONE)。
spark.deploy.recoveryDirectorySpark将存储恢复状态的目录,可从Master的角度访问。
  • 此解决方案可以与monit等过程监视器/管理器一起使用,或者只是通过重新启动来启用手动恢复。
  • 虽然文件系统恢复似乎比完全不进行任何恢复要好,但这种模式对于某些开发或实验目的而言可能不是最理想的。特别是,通过stop-master.sh查杀主服务器并不会清除其恢复状态,因此每当您启动一个新的主服务器时,它将进入恢复模式。如果需要等待所有先前注册的Workers /客户端超时,这可能会将启动时间增加最多1分钟。
  • 虽然它没有得到官方支持,但您可以将NFS目录挂载为恢复目录。如果原始主节点完全死亡,则可以在另一个节点上启动Master,这将正确恢复所有先前注册的Worker / applications(相当于ZooKeeper恢复)。然而,未来的应用程序必须能够找到新的Master,才能注册。


致谢

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值