文章目录
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
选举阶段:
- 对于standby Master来说,成为Active Master 也就意味着他在ZooKeeper中被选举成为了Leader。
Master启动切换阶段:
- 首先,前面的
masterInstance.electedLeader()
方法其实是让Standby Master给自己
发了一个ElectedLeader
消息,告诉自己已经成为了Leader
- 接着,使用
PersistenceEngine
去读取按其各自的id排序的持久化的storedApps, storedDrivers, storedWorkers
- 判断,如果storedApps, storedDrivers, storedWorkers中
有任何一个非空的
,将持久化的Application、Driver、Worker 的信息,重新进行注册
,注册到Master内部的内存缓存结构中
Master与Application通信阶段:
- 然后,将
Application、Worker
的状态都修改为UNKNOWN
,然后向Application,以及Worker发送一个MasterChanged()的消息,告知他们Active Master已经改变了,地址是当前这个Master的地址 - 理论上
Application和Worker
,理论上来说,如果它们目前都是正常在运行的话,那么在接收到Master发送来的地址之后,就会做出反应并返回响应消息给新Master
。
Master完成切换阶段:
- 此时,Master在陆续接收到Driver和Worker发送来的响应消息之后,会使用
completeRecovery()
方法对没有发送响应消息的Driver和Worker进行处理
,过滤
掉它们的信息。(1.内存缓存结构中移除 2.从相关组件的内存缓存中移除 3.从持久化存储中移除) - 最后,将自己的
状态修改为ALIVE
,再调用Master自己的schedule()
方法,对正在等待资源调度的Driver和Application进行调度
。比如在某个Worker上启动Driver,或者为Application在Worker上启动它需要的Executor。
图片资源地址: 基于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.recoveryDirectory | Spark将存储恢复状态的目录,可从Master的角度访问。 |
- 此解决方案可以与monit等过程监视器/管理器一起使用,或者只是通过重新启动来启用手动恢复。
- 虽然文件系统恢复似乎比完全不进行任何恢复要好,但这种模式对于某些开发或实验目的而言可能不是最理想的。特别是,通过stop-master.sh查杀主服务器并不会清除其恢复状态,因此每当您启动一个新的主服务器时,它将进入恢复模式。如果需要等待所有先前注册的Workers /客户端超时,这可能会将启动时间增加最多1分钟。
- 虽然它没有得到官方支持,但您可以将NFS目录挂载为恢复目录。如果原始主节点完全死亡,则可以在另一个节点上启动Master,这将正确恢复所有先前注册的Worker / applications(相当于ZooKeeper恢复)。然而,未来的应用程序必须能够找到新的Master,才能注册。