为了账号安全,请及时绑定邮箱和手机立即绑定

Spark-Core源码精读(7)、“Driver“服务启动流程解析

标签:
Spark

本文将解析Spark中Driver服务的开启流程,闲言少叙,直接进入源码。

首先Driver服务的开启是在创建Driver的运行时环境的时候完成的,如下所示:

SparkContext中:

// Create the Spark execution environment (cache, map output tracker, etc)_env = createSparkEnv(_conf, isLocal, listenerBus)SparkEnv.set(_env)

可以看到执行的是SparkEnv的createDriverEnv:

private[spark] def createSparkEnv(
    conf: SparkConf,
    isLocal: Boolean,
    listenerBus: LiveListenerBus): SparkEnv = {  // 创建Driver的运行时环境,注意这里的numDriverCores是local模式下用来执行计算的cores的个数,如果不是本地模式的话就是0
  SparkEnv.createDriverEnv(conf, isLocal, listenerBus, SparkContext.numDriverCores(master))
}

numDriverCores的计算:

/**
 * The number of driver cores to use for execution in local mode, 0 otherwise.
 */private[spark] def numDriverCores(master: String): Int = {  def convertToInt(threads: String): Int = {    if (threads == "*") Runtime.getRuntime.availableProcessors() else threads.toInt
  }
  master match {    case "local" => 1
    case SparkMasterRegex.LOCAL_N_REGEX(threads) => convertToInt(threads)    case SparkMasterRegex.LOCAL_N_FAILURES_REGEX(threads, _) => convertToInt(threads)    case _ => 0 // driver is not used for execution
  }
}

在SparkEnv中创建Driver运行时环境的代码:

/**
 * Create a SparkEnv for the driver.
 */private[spark] def createDriverEnv(
    conf: SparkConf,
    isLocal: Boolean,
    listenerBus: LiveListenerBus,
    numCores: Int,
    mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None): SparkEnv = {
  assert(conf.contains("spark.driver.host"), "spark.driver.host is not set on the driver!")
  assert(conf.contains("spark.driver.port"), "spark.driver.port is not set on the driver!")  val hostname = conf.get("spark.driver.host")  val port = conf.get("spark.driver.port").toInt
  create(
    conf,    SparkContext.DRIVER_IDENTIFIER,  // "driver"
    hostname,
    port,
    isDriver = true,
    isLocal = isLocal,
    numUsableCores = numCores,
    listenerBus = listenerBus,
    mockOutputCommitCoordinator = mockOutputCommitCoordinator
  )
}

我们在前面的文章中大致的浏览过,现在聚焦Driver服务启动相关的部分:

// 这里我们是Driver,所以actorSystemName是"sparkDriver"// 注意Spark2.x中已经移除了对Akka的依赖,所以在Spark2.x中这里是driverSystemName和executorSystemName// Create the ActorSystem for Akka and get the port it binds to.val actorSystemName = if (isDriver) driverActorSystemName else executorActorSystemName// 创建Driver的运行时环境,注意这里的clientMode等于falseval rpcEnv = RpcEnv.create(actorSystemName, hostname, port, conf, securityManager,
  clientMode = !isDriver)

接下来是RpcEnv的create方法:

def create(
    name: String,
    host: String,
    port: Int,
    conf: SparkConf,
    securityManager: SecurityManager,
    clientMode: Boolean = false): RpcEnv = {  // Using Reflection to create the RpcEnv to avoid to depend on Akka directly
  // 封装成RpcEnvConfig,这里的name是"sparkDriver",host是"driver",clientMode是"false"
  val config = RpcEnvConfig(conf, name, host, port, securityManager, clientMode)  // 这里实际上是通过反射的到的是NettyRpcEnvFactory,所以调用的是NettyRpcEnvFactory的create()方法
  getRpcEnvFactory(conf).create(config)
}

底层实现是NettyRpcEnvFactory的create方法:

def create(config: RpcEnvConfig): RpcEnv = {  val sparkConf = config.conf  // Use JavaSerializerInstance in multiple threads is safe. However, if we plan to support
  // KryoSerializer in future, we have to use ThreadLocal to store SerializerInstance
  val javaSerializerInstance =    new JavaSerializer(sparkConf).newInstance().asInstanceOf[JavaSerializerInstance]  // 实例化了NettyRpcEnv,名字为config.host,即driver
  val nettyEnv =    new NettyRpcEnv(sparkConf, javaSerializerInstance, config.host, config.securityManager)  // 传进来的clientMode为false,所以这里的判断为true
  if (!config.clientMode) {    // 定义了一个函数startNettyRpcEnv
    val startNettyRpcEnv: Int => (NettyRpcEnv, Int) = { actualPort =>
      nettyEnv.startServer(actualPort)      // 返回NettyRpcEnv及其端口号
      (nettyEnv, nettyEnv.address.port)
    }    try {      // 开启“sparkDriver”服务,注意此处传进了上面定义的函数,这里的config.name是"sparkDriver",最后返回了NettyRpcEnv
      Utils.startServiceOnPort(config.port, startNettyRpcEnv, sparkConf, config.name)._1
    } catch {      case NonFatal(e) =>
        nettyEnv.shutdown()        throw e
    }
  }  // 返回NettyRpcEnv
  nettyEnv
}

Utils中的startServiceOnPort方法:

def startServiceOnPort[T](
    startPort: Int,
    startService: Int => (T, Int),
    conf: SparkConf,
    serviceName: String = ""): (T, Int) = {  // 我们传进来的startPort为0,所以会生成一个随机的端口号
  require(startPort == 0 || (1024 <= startPort && startPort < 65536),    "startPort should be between 1024 and 65535 (inclusive), or 0 for a random free port.")  // " 'sparkDriver'"
  val serviceString = if (serviceName.isEmpty) "" else s" '$serviceName'"
  // 通过"spark.port.maxRetries"设置,如果没有设置,而设置中包括"spark.testing",
  // 最大重试次数就是100次,否则最大重试次数就是10次
  val maxRetries = portMaxRetries(conf)  for (offset <- 0 to maxRetries) {    // 设置端口号
    // Do not increment port if startPort is 0, which is treated as a special port
    val tryPort = if (startPort == 0) {
      startPort
    } else {      // If the new port wraps around, do not try a privilege port
      ((startPort + offset - 1024) % (65536 - 1024)) + 1024
    }    try {      // 开启服务,并返回服务和端口号,注意这里的startService是上面传进来的那个函数startNettyRpcEnv
      // 所以我们实际上执行的是startNettyRpcEnv(tryPort),而根据startNettyRpcEnv函数的定义,实际
      // 上是调用了nettyEnv.startServer(tryPort)方法
      val (service, port) = startService(tryPort)      // 创建成功后打印日志,serviceString就是"sparkDriver"
      logInfo(s"Successfully started service$serviceString on port $port.")      // 返回服务和端口号
      return (service, port)
    } catch {      case e: Exception if isBindCollision(e) =>        if (offset >= maxRetries) {          val exceptionMessage = s"${e.getMessage}: Service$serviceString failed after " +            s"$maxRetries retries! Consider explicitly setting the appropriate port for the " +            s"service$serviceString (for example spark.ui.port for SparkUI) to an available " +            "port or increasing spark.port.maxRetries."
          val exception = new BindException(exceptionMessage)          // restore original stack trace
          exception.setStackTrace(e.getStackTrace)          throw exception
        }
        logWarning(s"Service$serviceString could not bind on port $tryPort. " +          s"Attempting port ${tryPort + 1}.")
    }
  }  // Should never happen
  throw new SparkException(s"Failed to start service$serviceString on port $startPort")
}

下面我们就具体看一下NettyRpcEnv中的这个startServer方法,具体的启动方法我们不再追踪了,最后实际上创建了一个TransportServer。

def startServer(port: Int): Unit = {  // 首先实例化bootstraps
  val bootstraps: java.util.List[TransportServerBootstrap] =    if (securityManager.isAuthenticationEnabled()) {
      java.util.Arrays.asList(new SaslServerBootstrap(transportConf, securityManager))
    } else {
      java.util.Collections.emptyList()
    }  // 实例化server
  server = transportContext.createServer(host, port, bootstraps)  // 向dispatcher注册
  dispatcher.registerRpcEndpoint(    RpcEndpointVerifier.NAME, new RpcEndpointVerifier(this, dispatcher))
}

再回到SparkEnv中,开启了"sparkDriver"服务后,又创建了Akka的ActorSystem,具体的创建过程就不分析了。

// 开启了sparkDriverActorSystem服务,spark2.x中已经移除了对Akka的依赖val actorSystem: ActorSystem =  if (rpcEnv.isInstanceOf[AkkaRpcEnv]) {
    rpcEnv.asInstanceOf[AkkaRpcEnv].actorSystem
  } else {    val actorSystemPort =      if (port == 0 || rpcEnv.address == null) {
        port
      } else {
        rpcEnv.address.port + 1
      }    // Create a ActorSystem for legacy codes
    AkkaUtils.createActorSystem(
      actorSystemName + "ActorSystem",
      hostname,
      actorSystemPort,
      conf,
      securityManager
    )._1
  }  
// 最后使用开启的服务的端口替换掉原来的端口if (isDriver) {
  conf.set("spark.driver.port", rpcEnv.address.port.toString)
} else if (rpcEnv.address != null) {
  conf.set("spark.executor.port", rpcEnv.address.port.toString)
}

我们使用spark-submit的client模式提交应用程序时,就可以看到关于这部分的日志信息:

17/03/02 09:38:28 INFO Utils: Successfully started service 'sparkDriver' on port 33861.
17/03/02 09:38:29 INFO Slf4jLogger: Slf4jLogger started
17/03/02 09:38:29 INFO Remoting: Starting remoting
17/03/02 09:38:29 INFO Remoting: Remoting started; listening on addresses :[akka.tcp://sparkDriverActorSystem@172.17.0.2:34803]
17/03/02 09:38:29 INFO Utils: Successfully started service 'sparkDriverActorSystem' on port 34803.

注意:本文基于的是Spark 1.6.3版本的源码,并对Spark 2.x版本的改变进行了相应的说明,这里给出具体的连接供大家参考:

Spark 1.6.3 源码

Spark 2.1.0 源码

本文为原创,欢迎转载,转载请注明出处、作者,谢谢!



作者:sun4lower
链接:https://www.jianshu.com/p/b948318bfd59


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消