在一个分布式系统中,非常重要的一点就是容错性,Spark也不例外,当它机器发生故障的时候,可以很轻松的应对。本篇容错机制的剖析主要针对Standalone模式进行分析。
阅读本篇文章之前,你可以查看之前的【Spark源码解读之Master剖析】以及【Spark源码解读之Worker剖析】的文章,该篇是基于之前文章的补充。
在一个Spark集群中,有各种角色,Executor、Worker、Master等。下面来分析它们发生故障的时候该如何解决。
Executor异常退出
当Executor因为故障退出,那么势必影响运行在其上的任务无法正常运行。在前面【Spark源码解读之Worker剖析】一文中,通过源码我们了解到,启动Worker节点它会启动CoarseGrainedExecutorBackend
进程,一旦收到EXITED
退出状态,那么它会向Worker节点发送ExecutorStateChange
的消息,接着又向Master
转发ExecutorStateChange
消息,在收到消息之后,Master会做以下几件事情:
- 找到占有Executor的Application的ApplicationInfo,以及Executor对应的ExecutorInfo。
- 将ExecutorInfo的状态改为
EXITED
。 EXITED
也属于Executor完成状态,所以会将ExecutorInfo从ApplicationInfo和WorkerInfo中删除。- 由于Executor是非正常退出,那么Master会调用
schedule
方法重新进行资源的调度。
Worker异常退出
当Worker节点发生故障,异常退出的时候,它会启动一个shutdownHook
线程,在这个线程内调用killProcess
方法主动杀死Executor的后台进程CoarseGrainedExecutorBackend
进程,然后收到进程返回的EXITED
状态,Worker会向Master发送ExecutorStateChanged
消息,由于Worker发生故障宕机了,不会发送心跳报告给Master,所以Master收到worker的心跳报告,那么它会该Worker节点的WorkerInfo信息,然后将该Worker中的所有Executor以LOST
的状态同步更新到Driver Application
,最后Master还会重新调度Driver,将任务分配到其他Worker节点中执行。
/**
* 启动Executor进程
*/
def start() {
//创建JAVA线程
workerThread = new Thread("ExecutorRunner for " + fullId) {
override def run() { fetchAndRunExecutor() }
}
workerThread.start()
// Shutdown hook that kills actors on shutdown.
shutdownHook = new Thread() {
override def run() {
killProcess(Some("Worker shutting down"))
}
}
Runtime.getRuntime.addShutdownHook(shutdownHook)
}
/**
* Kill executor process, wait for exit and notify worker to update resource status.
*
* @param message the exception message which caused the executor's death
*/
private def killProcess(message: Option[String]) {
var exitCode: Option[Int] = None
if (process != null) {
logInfo("Killing process!")
if (stdoutAppender != null) {
stdoutAppender.stop()
}
if (stderrAppender != null) {
stderrAppender.stop()
}
process.destroy()
exitCode = Some(process.waitFor())
}
worker ! ExecutorStateChanged(appId, execId, state, message, exitCode)
}
Master异常退出
假设在Standalone集群中,我们只有一个master,那么当它发生故障的时候会发生什么事呢?
- 当Executor上任务的执行完毕,需要对资源进行回收时。在SparkContext中我们调用stop方法来停止任务,Driver会向给自己服务的Executor发送
StopExecutor
消息对资源进行回收,Master虽然跑路了,但是Driver依然持有这些Executor,只是Master收不到DisassociatedEvent
消息,但是CoarseGrainedExecutorBackend
任然可以收到该消息,退出进程,所以说Master挂掉,对于如果Worker,Executor正常运行,对于资源的回收没有任何问题。 - 如果此时Executor再发生异常,Worker节点无法告知Master节点,也不能通过Driver重新调度,所以此时Executor上的任务无法执行,资源也无法回收。
- 如果此时Worker节点再发生异常,那么Worker和Executor都将停止服务,由于Master无法收到消息,该节点上的任务也执行失败,Driver提交的任务也无法执行,Worker节点资源无法被利用。
- 如果有新的Driver提交任务也无法成功。
可见,对于一个集群,只有一个Master是不可行的。因此我们需要HA方式来解决单点故障。这里涉及到两种机制。
持久化引擎:
- ZookeeperPersistenceEngine:基于Zookeeper实现的持久化引擎。
- FileSystemPersistenceEngine:基于文件系统的持久化引擎。
领导选举机制:
- ZookeeperLeaderElectionAgent:对Zookeeper提供的选举机制的代理。
- MoarchyLeaderAgent:默认的选举机制。
默认的情况下,Spark不提供故障恢复的能力。
val RECOVERY_MODE = conf.get("spark.deploy.recoveryMode", "NONE")
val RECOVERY_DIR = conf.get("spark.deploy.recoveryDirectory", "")
所以当我们没有设置这两个参数的时候,RECOVERY_MODE
为NONE
。
如果使用FileSystemPersistenceEngine
时,对应的领导选举机制是MoarchyLeaderAgent
选举机制,我们可以将参数spark.deploy.recoveryMode
设置为FILESYSTEM
,然后可以通过给FileSystemPersistenceEngine
设置构造参数来设置RECOVERY_DIR
的值。
如果使用ZookeeperPersistenceEngine
时,对应的领导选举机制为ZookeeperLeaderElectionAgent
,可以通过设置参数spark.deploy.recoveryMode
为ZOOKEEPER
。这时集群会通过Zookeeper来实现自动故障转移。
在后续的文章中,会接着探讨Spark的选举机制以及基于Yarn和Mesos的集群部署模式,欢迎持续关注。
欢迎加入大数据学习交流群:731423890