SparkCore — Master主备切换

Master实际上是可以配置两个的,Standalone模式下也支持主备切换,也就是说当Active Master节点挂掉的时候,standby Master就会切换为Active Master。

Spark Master主备切换一般常用的有两种机制:一个是基于文件系统的;一个是基于Zookeeper。基于文件系统的主备切换闷在主Master节点挂掉之后,需要手动切换到Standby节点上;而基于Zookeeper可以实现自动切换。

下面我们看看在主备切换的时候,Master做了什么事情,主要流程如下所示,然后针对流程对源码进行分析:

下面我们针对上述流程来看源码:

在发生主备切换的时候,Master会接收到ElectedLeader消息:

override def receive: PartialFunction[Any, Unit] = {
    case ElectedLeader => {
      // 从持久化引擎中读取storedApps、storedDrivers、storedWorkers
      val (storedApps, storedDrivers, storedWorkers) = persistenceEngine.readPersistedData(rpcEnv)
      // 判断这三个状态
      state = if (storedApps.isEmpty && storedDrivers.isEmpty && storedWorkers.isEmpty) {
        // 如果都是空的说明,Active Master还存活,不需要进行切换
        RecoveryState.ALIVE
      } else {
        // 将状态改变为,recovering状态
        RecoveryState.RECOVERING
      }
      logInfo("I have been elected leader! New state: " + state)
      if (state == RecoveryState.RECOVERING) {
        // 开始进行状态切换
        beginRecovery(storedApps, storedDrivers, storedWorkers)
        // 状态切换完成,就发送一个CompleteRecovery消息
        recoveryCompletionTask = forwardMessageThread.schedule(new Runnable {
          override def run(): Unit = Utils.tryLogNonFatalError {
            self.send(CompleteRecovery)
          }
        }, WORKER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
      }
    }

    // 接收到状态切换完成的消息,调用completeRecovery()方法过滤掉已经挂掉的节点信息
    // 并使用schedule()方法,重新进行资源调度
    case CompleteRecovery => completeRecovery()

    // 省略代码若干
    ..................

}

从上面的源码可以看出,当发生主备切换的时候,Standby Master会接收到一个ElectedLeader的消息,standby master接收到之后,从持久化引擎中读取App、Driver和Worker的信息,假如读取到了,那么说明active master挂掉了,那么这时候,standby master,状态改变为recovering,开始进行master节点恢复。

首先调用beginRecovery()对App、Worker和Driver进行恢复,发送MasterChanged的消息给App Driver和Worker,并将当前App和Worker状态更新为UNKNOW

等待接收到反馈的消息之后,就会发送一个CompleteRecovery的消息,master在接收到这个消息之后,就调用completeRecovery()方法,对App、Worker进行过滤,将除了UNKNOW的App和Worker进行移除(过滤掉出故障的或者已经完成的Application),接着在调用schedule()方法重新进行资源调度。

下面是beginRecovery()和completeRecovery()方法,配上相关代码注释帮助理解:

private def beginRecovery(storedApps: Seq[ApplicationInfo], storedDrivers: Seq[DriverInfo],
      storedWorkers: Seq[WorkerInfo]) {
    // 遍历Application
    for (app <- storedApps) {
      logInfo("Trying to recover app: " + app.id)
      try {
        // 对app信息进行封装,保存Application的相关信息
        registerApplication(app)
        // 状态更改为UNKNOW
        app.state = ApplicationState.UNKNOWN
        // 向Driver发送MasterChanged信息,包括了master的地址
        app.driver.send(MasterChanged(self, masterWebUiUrl))
      } catch {
        case e: Exception => logInfo("App " + app.id + " had exception on reconnect")
      }
    }

    // driver信息就保存在本地缓存即可
    for (driver <- storedDrivers) {
      // Here we just read in the list of drivers. Any drivers associated with now-lost workers
      // will be re-launched when we detect that the worker is missing.
      drivers += driver
    }

    // 遍历Worker信息,状态更改和向worker发送消息
    for (worker <- storedWorkers) {
      logInfo("Trying to recover worker: " + worker.id)
      try {
        registerWorker(worker)
        worker.state = WorkerState.UNKNOWN
        worker.endpoint.send(MasterChanged(self, masterWebUiUrl))
      } catch {
        case e: Exception => logInfo("Worker " + worker.id + " had exception on reconnect")
      }
    }
  }

上面代码就是beginRecovery(),对App和Worker进行状态改变为UNKNOW,并发送standby master的URL地址到对应的worker和Application Driver节点上。

       当注册结束之后,当前状态就会变为CompleteRecovery,standby master接收到这个消息之后,就调用如下方法:

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

    // Kill off any workers and apps that didn't respond to us.
    // 过滤掉目前状态还是UNKONW的App和Worker
    // 删除出故障或死掉的worker,或完成的Application
    // 总结一下清理机制:1、从内存缓存结构中移除;2、从相关组件的内存缓存中移除;3、从持久化存储中移除
    workers.filter(_.state == WorkerState.UNKNOWN).foreach(removeWorker)
    apps.filter(_.state == ApplicationState.UNKNOWN).foreach(finishApplication)

    // Reschedule drivers which were not claimed by any workers
    // 如果Driver为空,那么假如配置了supervise这个选项,就会尝试启动Driver
    // 否则就移除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")
      }
    }

    // Standalone Master状态变为 Active Master
    state = RecoveryState.ALIVE
    // 重新进行资源调度
    schedule()
    logInfo("Recovery complete - resuming operations!")
  }

在调用completeRecovery()方法之后,首先将master状态更改为completing_recovery,接着过滤掉不是UNKNOW的App和Worker,这里过滤机制主要是:从内存缓存中移除掉相关信息;从相关组件中移除;从持久化存储中移除。接着判断Driver上是否是空节点,假如是,并且设置了supervise,那么尝试重新launch,否则就删除。

最后将standalone master 变为 active master,这里就master就切换完成,接着调用schedule()重新进行资源调度。

### 如何在 IntelliJ IDEA 中配置并运行 Spark 项目 #### 配置环境 要在 IntelliJ IDEA 中配置并运行 Spark 项目,需要确保正确设置开发环境。首先,创建一个新的 Maven 项目,并在 `pom.xml` 文件中添加 Spark 相关的依赖项[^1]。例如,对于 Spark 的 Java API,可以使用以下依赖: ```xml <dependencies> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_2.12</artifactId> <version>3.3.0</version> </dependency> </dependencies> ``` 此外,还需要配置 Spark 的运行环境。如果计划在本地运行 Spark 程序,可以通过设置 `SPARK_HOME` 环境变量来指定 Spark 的安装路径[^2]。 #### 创建 Spark 程序 创建一个 Java 类文件,并编写 Spark 程序的基本代码结构。以下是一个简单的 Spark 程序示例,展示了如何初始化 Spark 上下文并执行基本操作: ```java package com.example.spark; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; public class SparkDemo { public static void main(String[] args) { // 初始化 Spark 配置 SparkConf conf = new SparkConf() .setAppName("Spark Demo") .setMaster("local[*]"); // 设置为本地模式 // 创建 Spark 上下文 try (JavaSparkContext sc = new JavaSparkContext(conf)) { // 创建 RDD 并执行简单操作 JavaRDD<String> rdd = sc.parallelize(java.util.Arrays.asList("hello", "world")); long count = rdd.count(); System.out.println("Count: " + count); } } } ``` #### 运行 Spark 程序 在 IntelliJ IDEA 中运行 Spark 程序时,需要确保 JVM 参数正确配置。可以通过以下步骤实现: 1. 右键点击类文件(如上述代码中的 `SparkDemo`),选择“Run”。 2. 在运行配置中,添加必要的 JVM 参数,例如 `-Dspark.master=local[*]`,以指定 Spark 的运行模式为本地模式[^1]。 #### 提交到集群 当程序在本地测试完成后,可以将其提交到 Spark 的本地模式或 Hadoop YARN 集群运行。通过修改 `setMaster` 方法的参数,可以切换运行模式。例如,将 `setMaster("local[*]")` 替换为 `setMaster("yarn")` 即可提交到 YARN 集群运行[^2]。 ```java SparkConf conf = new SparkConf() .setAppName("Spark Demo") .setMaster("yarn"); // 提交到 YARN 集群 ``` #### 注意事项 - 如果使用 Scala 编写 Spark 程序,则需要在 IntelliJ IDEA 中安装 Scala 插件,并按照类似的步骤进行配置。 - 确保项目中包含所有必要的依赖项,并且版本兼容性良好。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值