5. Spark源码解析之worker启动流程解析

启动Spark的Worker,不管是执行start-all.sh还是直接在Worker节点直接启动start-slaves.sh,都会进入start-slave.sh开始,而基本都不会设置参数,所以参数都是默认的--webui-port 8081 spark://host:7077。

脚本阶段跟启动Master步骤差不多,执行流程可以参照Master:4. Spark源码解析之Master启动流程解析

start-slave.sh会调用spark-daemon.sh,spark-daemon.sh会调用spark-class,然后再调用launch.main来解析生成命令,最后返回给spark-class的exec "${CMD[@]}" 执行,执行其实才是真正启动Worker。

最终的命令是:java -cp ../conf/:../jars/* -Xmx1g org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://host:7077

这里从Worker的开始,仍然先进入伴生对象的main方法。

 

org.apache.spark.deploy.worker.Worker

private[deploy] object Worker extends Logging {
  val SYSTEM_NAME = "sparkWorker"
  val ENDPOINT_NAME = "Worker"

  // 参数:--webui-port 8081 spark://host:7077
  def main(argStrings: Array[String]) {
    Thread.setDefaultUncaughtExceptionHandler(new SparkUncaughtExceptionHandler(
      exitOnUncaughtException = false))
    Utils.initDaemon(log)
    // 加载默认配置
    val conf = new SparkConf
    // 加载参数和配置进行解析,得到启动RPC和worker需要的参数
    // worker-host、worker-port、webUI-port、cores、memory、master、workerDir、properties
    // 这个类在下面另外进行解读
    val args = new WorkerArguments(argStrings, conf)
    // 启动RPC和worker终端
    val rpcEnv = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores,
      args.memory, args.masters, args.workDir, conf = conf)
    // With external shuffle service enabled, if we request to launch multiple workers on one host,
    // we can only successfully launch the first worker and the rest fails, because with the port
    // bound, we may launch no more than one external shuffle service on each host.
    // When this happens, we should give explicit reason of failure instead of fail silently. For
    // more detail see SPARK-20989.
    // 如果开启shuffle服务,只能启动一台worker,因为会绑定端口,导致其他多台worker启动失败,所以这里关闭
    val externalShuffleServiceEnabled = conf.getBoolean("spark.shuffle.service.enabled", false)
    // worker实例,默1个
    val sparkWorkerInstances = scala.sys.env.getOrElse("SPARK_WORKER_INSTANCES", "1").toInt
    // 判断shuffle状态和worker实例个数,有误则提示
    require(externalShuffleServiceEnabled == false || sparkWorkerInstances <= 1,
      "Starting multiple workers on one host is failed because we may launch no more than one " +
        "external shuffle service on each host, please set spark.shuffle.service.enabled to " +
        "false or set SPARK_WORKER_INSTANCES to 1 to resolve the conflict.")
    // RPC启动后处理等待worker注册状态
    rpcEnv.awaitTermination()
  }
}

WorkerArguments

这个类主要是加载参数和默认配置进行解析。

private[worker] class WorkerArguments(args: Array[String], conf: SparkConf) {
  // 调用Utils类的方法获取运行所在host
  // 除了cores和memory是获取运行服务器的配置,其他默认为空
  var host = Utils.localHostName()
  var port = 0
  var webUiPort = 8081
  // 通过方法获取当前服务器的总cores和memory的配置
  var cores = inferDefaultCores()
  var memory = inferDefaultMemory()
  var masters: Array[String] = null
  var workDir: String = null
  var propertiesFile: String = null

  // Check for settings in environment variables
  // 先检查环境变量中的设置,其实是前面已经先加载了spark-env中配置导入到环境变量
  if (System.getenv("SPARK_WORKER_PORT") != null) {
    port = System.getenv("SPARK_WORKER_PORT").toInt
  }
  if (System.getenv("SPARK_WORKER_CORES") != null) {
    cores = System.getenv("SPARK_WORKER_CORES").toInt
  }
  if (conf.getenv("SPARK_WORKER_MEMORY") != null) {
    memory = Utils.memoryStringToMb(conf.getenv("SPARK_WORKER_MEMORY"))
  }
  if (System.getenv("SPARK_WORKER_WEBUI_PORT") != null) {
    webUiPort = System.getenv("SPARK_WORKER_WEBUI_PORT").toInt
  }
  if (System.getenv("SPARK_WORKER_DIR") != null) {
    workDir = System.getenv("SPARK_WORKER_DIR")
  }

  // 在这里对启动时定义的参数进行解析
  parse(args.toList)

  // This mutates the SparkConf, so all accesses to it must be made after this line
  // 这里加载了conf设置,所以会改变参数,也就是跟环境变量进行比较和更改
  // propertiesFile上面默认设置为空,--properties-file没有设置,所以还是空
  propertiesFile = Utils.loadDefaultSparkProperties(conf, propertiesFile)

  // 如果有设置worker的port,就获取
  if (conf.contains("spark.worker.ui.port")) {
    webUiPort = conf.get("spark.worker.ui.port").toInt
  }

  // 检查内存,方法在这个类的最下面
  checkWorkerMemory()

  @tailrec
  // 解析输入参数的方法parse
  // 其实就是解析 --string string,判断--后的string,如果匹配,把后面的string赋值给value
  private def parse(args: List[String]): Unit = args match {
    case ("--ip" | "-i") :: value :: tail =>
      Utils.checkHost(value)
      host = value
      parse(tail)

    case ("--host" | "-h") :: value :: tail =>
      Utils.checkHost(value)
      host = value
      parse(tail)

    case ("--port" | "-p") :: IntParam(value) :: tail =>
      port = value
      parse(tail)

    case ("--cores" | "-c") :: IntParam(value) :: tail =>
      cores = value
      parse(tail)

    case ("--memory" | "-m") :: MemoryParam(value) :: tail =>
      memory = value
      parse(tail)

    case ("--work-dir" | "-d") :: value :: tail =>
      workDir = value
      parse(tail)
    // 传入的参数有 --webui-port 这里会进行解析
    case "--webui-port" :: IntParam(value) :: tail =>
      webUiPort = value
      parse(tail)

    case ("--properties-file") :: value :: tail =>
      propertiesFile = value
      parse(tail)

    case ("--help") :: tail =>
      printUsageAndExit(0)

    // 只有一个参数也就是master,如传入的参数有spark://host:7077,在这里解析
    case value :: tail =>
      if (masters != null) {  // Two positional arguments were given
        printUsageAndExit(1)
      }

      // 解析master
      masters = Utils.parseStandaloneMasterUrls(value)
      // 这个方法:stripPrefix("spark://").split(",").map("spark://" + _)
      // 将值再传回parse方法判断,也就是上面和下面这里,case value和case Nil
      parse(tail)

    // 如果master不存在,提示并错误码1退出
    case Nil =>
      if (masters == null) {  // No positional argument was given
        printUsageAndExit(1)
      }

    // 其他不符合规则的参数提示错误码1退出
    case _ =>
      printUsageAndExit(1)
  }

  /**
   * Print usage and exit JVM with the given exit code.
   */
  // master不存在和参数错误的提示和退出方法
  def printUsageAndExit(exitCode: Int) {
    // scalastyle:off println
    // scalastyle是scala风格检查
    System.err.println(
      "Usage: Worker [options] <master>\n" +
      "\n" +
      "Master must be a URL of the form spark://hostname:port\n" +
      "\n" +
      "Options:\n" +
      "  -c CORES, --cores CORES  Number of cores to use\n" +
      "  -m MEM, --memory MEM     Amount of memory to use (e.g. 1000M, 2G)\n" +
      "  -d DIR, --work-dir DIR   Directory to run apps in (default: SPARK_HOME/work)\n" +
      "  -i HOST, --ip IP         Hostname to listen on (deprecated, please use --host or -h)\n" +
      "  -h HOST, --host HOST     Hostname to listen on\n" +
      "  -p PORT, --port PORT     Port to listen on (default: random)\n" +
      "  --webui-port PORT        Port for web UI (default: 8081)\n" +
      "  --properties-file FILE   Path to a custom Spark properties file.\n" +
      "                           Default is conf/spark-defaults.conf.")
    // scalastyle:on println
    System.exit(exitCode)
  }

  // 获取运行时的cores
  def inferDefaultCores(): Int = {
    Runtime.getRuntime.availableProcessors()
  }

  // 获取内存配置
  def inferDefaultMemory(): Int = {
    // java版本为IBM的,即"java.vendor"包含IBM,默认设置内存为0
    val ibmVendor = System.getProperty("java.vendor").contains("IBM")
    var totalMb = 0
    try {
      // scalastyle:off classforname
      // 通过工厂模式获取系统最大内存,首先获取系统的管理Bean
      val bean = ManagementFactory.getOperatingSystemMXBean()
      // IBM版本通过反射获取系统最大内存
      if (ibmVendor) {
        // 反射获取bean内存的类
        val beanClass = Class.forName("com.ibm.lang.management.OperatingSystemMXBean")
        // 通过类反射获取方法
        val method = beanClass.getDeclaredMethod("getTotalPhysicalMemory")
        // 调用方法计算出bean最大内存,转成MB格式的int类型
        totalMb = (method.invoke(bean).asInstanceOf[Long] / 1024 / 1024).toInt
      } else {
        // sun版本java,反射获取内存
        val beanClass = Class.forName("com.sun.management.OperatingSystemMXBean")
        val method = beanClass.getDeclaredMethod("getTotalPhysicalMemorySize")
        totalMb = (method.invoke(bean).asInstanceOf[Long] / 1024 / 1024).toInt
      }
      // scalastyle:on classforname
    } catch {
      // 设置有错误则提示不足2G使用内存
      case e: Exception =>
        totalMb = 2*1024
        // scalastyle:off println
        System.out.println("Failed to get total physical memory. Using " + totalMb + " MB")
        // scalastyle:on println
    }
    // Leave out 1 GB for the operating system, but don't return a negative memory size
    // 这里将获取的内存为系统运行留1G
    // driver的默认设置内存为1G
    // 获取的内存与driver默认内存进行比较,取大值
    math.max(totalMb - 1024, Utils.DEFAULT_DRIVER_MEM_MB)
  }

  // 检查内存配置,没有设置则报错异常退出
  def checkWorkerMemory(): Unit = {
    if (memory <= 0) {
      val message = "Memory is below 1MB, or missing a M/G at the end of the memory specification?"
      throw new IllegalStateException(message)
    }
  }
}

 startRpcEnvAndEndpoint

这里主要初始化了securityManager,RPC,以及注册Worker到RPC,并在执行rpcEnv.setupEndpoint()时,new Worker进行Worker实例化。


  // 传入上面的参数,启动RPC和worker终端
  // 例如在本地启动的,这里传入参数为:
  // host=192.168.2.1
  // port=0
  // webUIport=8081
  // cores=8
  // memory=7050
  // masterUrls=Array(spark://192.168.2.1:7077)
  // workerDir=null
  // properties=null
  // 多了一个参数,worker实例个数workerNumber
  def startRpcEnvAndEndpoint(
      host: String,
      port: Int,
      webUiPort: Int,
      cores: Int,
      memory: Int,
      masterUrls: Array[String],
      workDir: String,
      workerNumber: Option[Int] = None,
      conf: SparkConf = new SparkConf): RpcEnv = {

    // The LocalSparkCluster runs multiple local sparkWorkerX RPC Environments
    // 在spark集群上运行多个worker的RPC环境
    // 这的名字其实就是最开始设置的SYSTEM_NAME+实例数,默认单实例这里就是"sparkWorker"
    val systemName = SYSTEM_NAME + workerNumber.map(_.toString).getOrElse("")
    // 初始化securityManager,后续另外解析securityManager组件
    val securityMgr = new SecurityManager(conf)
    // 初始化Rpc容器,RPC组件也在后续另外单独解析
    val rpcEnv = RpcEnv.create(systemName, host, port, conf, securityMgr)
    // 这里解析了master的host和port
    val masterAddresses = masterUrls.map(RpcAddress.fromSparkURL(_))
    // 将worker注册到RPC,并真正实例化
    rpcEnv.setupEndpoint(ENDPOINT_NAME, new Worker(rpcEnv, webUiPort, cores, memory,
      masterAddresses, ENDPOINT_NAME, workDir, conf, securityMgr))
    //返回初始化好的RPC
    rpcEnv
  }

startRpcEnvAndEndpoint完成,会返回main中继续往下执行,完成RPC启动和Worker注册,当执行到rpcEnv.awaitTermination()时,会处理等待任务提交状态,Worker的启动就完成了。

当提交任务的时候,Worker会进行相应的方法调用。后面再详细解读Worker的实例化。

Worker实例化流程解析

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

訾零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值