Master启动的源码详解

5.1.2 Master启动的源码详解

 

Spark中各个组件是通过脚本来启动部署的,下面以脚本为入口点开始分析Master的部署。每个组件对应提供了启动的脚本,同时也会提供停止的脚本,停止脚本比较简单,在此仅分析启动脚本。

1.      Master部署的启动脚本解析

首先看下Master的启动脚本./sbin/start-master.sh,内容如下:

1.          # 在脚本的执行节点启动Master组件

2.          # Starts the master on the machine this script is executed on.

3.           

4.          #如果没有设置环境变量SPARK_HOME,会根据脚本所在位置自动设置

5.          if [ -z "${SPARK_HOME}" ]; then

6.            export SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"

7.          fi

8.           

9.          # NOTE: This exact class name is matched downstream by SparkSubmit.

10.       # Any changes need to be reflected there.

11.       # Master 组件对应的类

12.       CLASS="org.apache.spark.deploy.master.Master"

13.        

14.       #脚本的帮助信息

15.       if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; then

16.         echo "Usage: ./sbin/start-master.sh [options]"

17.         pattern="Usage:"

18.         pattern+="\|Using Spark's default log4j profile:"

19.         pattern+="\|Registered signal handlers for"

20.        

21.       # 通过脚本spark-class执行指定的Master类,参数为--help

22.         "${SPARK_HOME}"/bin/spark-class $CLASS --help 2>&1 | grep -v "$pattern" 1>&2

23.         exit 1

24.       fi

25.        

26.       ORIGINAL_ARGS="$@"

27.        

28.       #控制启动Master时,是否同时启动 Tachyon的Master组件

29.       START_TACHYON=false

30.        

31.       while (( "$#" )); do

32.       case $1 in

33.           --with-tachyon)

34.             if [ ! -e "${SPARK_HOME}"/tachyon/bin/tachyon ]; then

35.               echo "Error: --with-tachyon specified, but tachyon not found."

36.               exit -1

37.             fi

38.             START_TACHYON=true

39.             ;;

40.       esac

41.       shift

42.       done

43.        

44.       . "${SPARK_HOME}/sbin/spark-config.sh"

45.        

46.       . "${SPARK_HOME}/bin/load-spark-env.sh"

47.        

48.       #下面的一些参数对应的默认的配置属性

49.       if [ "$SPARK_MASTER_PORT" = "" ]; then

50.         SPARK_MASTER_PORT=7077

51.       fi

52.        

53.       // 用于MasterURL,所以当没有设置时,默认会使用hostname而不是IP地址

54.       // 该MasterURL在Worker注册或应用程序提交时使用

55.       if [ "$SPARK_MASTER_IP" = "" ]; then

56.         SPARK_MASTER_IP=`hostname`

57.       fi

58.        

59.       if [ "$SPARK_MASTER_WEBUI_PORT" = "" ]; then

60.         SPARK_MASTER_WEBUI_PORT=8080

61.       fi

62.        

63.       #通过启动后台进程的脚本spark-daemon.sh来启动Master组件

64.       "${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS 1 \

65.         --ip $SPARK_MASTER_IP --port $SPARK_MASTER_PORT --webui-port $SPARK_MASTER_WEBUI_PORT \

66.         $ORIGINAL_ARGS

67.        

68.       #需要时同时启动Tachyon,此时Tachyon是编译在Spark内的

69.       if [ "$START_TACHYON" == "true" ]; then

70.         "${SPARK_HOME}"/tachyon/bin/tachyon bootstrap-conf $SPARK_MASTER_IP

71.         "${SPARK_HOME}"/tachyon/bin/tachyon format -s

72.         "${SPARK_HOME}"/tachyon/bin/tachyon-start.sh master

73.       fi

通过脚本的简单分析,可以看出Master组件是以后台守护进程的方式启动的,对应的后台守护进程的启动脚本spark-daemon.sh,在后台守护进程的启动脚本spark-daemon.sh内部,通过脚本spark-class来启动一个指定主类的JVM进程,相关代码如下所示:

1.          case "$mode" in

2.          #这里对应的是启动一个Spark类

3.              (class)

4.          nohup nice -n "$SPARK_NICENESS" "${SPARK_HOME}"/bin/spark-class $command "$@" >> "$log" 2>&1 < /dev/null &

5.          newpid="$!"

6.                ;;

7.          #这里对应提交一个Spark 应用程序

8.              (submit)

9.          nohup nice -n "$SPARK_NICENESS" "${SPARK_HOME}"/bin/spark-submit --class $command "$@" >> "$log" 2>&1 < /dev/null &

10.       newpid="$!"

11.             ;;

12.        

13.           (*)

14.             echo "unknown mode: $mode"

15.             exit 1

16.             ;;

 

通过脚本的分析,可以知道最终执行的是Master类(对应的代码为前面的CLASS="org.apache.spark.deploy.master.Master"),对应的入口点则是Master伴生对象中的main方法,下面以该方法作为入口点进一步解析Master部署框架。

在部署Master组件时,最简单的方式是直接启动脚本,不带任何选项参数,命令如下所示:

1.          ./sbin/start-master.sh

如需设置选项参数,可以查看帮助信息,根据自己的需要进行设置。

2.      Master的源码解析

首先查看Master伴生对象中的main方法。

Master.scala源码:

1.           def main(argStrings: Array[String]) {

2.             Utils.initDaemon(log)

3.             val conf = new SparkConf

4.           // 构建参数解析的实例

5.             val args = new MasterArguments(argStrings,conf)

6.            // 启动RPC通信环境以及Master的RPC通信终端

7.             val (rpcEnv, _, _) =startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, conf)

8.             rpcEnv.awaitTermination()

9.           }

 

和其他类,如SparkSubmit一样,Master类的入口点处也包含了对应的参数类MasterArguments, MasterArguments类包括Spark属性配置相关的一些解析。

MasterArguments.scala源码如下:

1.          private[master] class MasterArguments(args:Array[String], conf: SparkConf) extends Logging {

2.           var host = Utils.localHostName()

3.           var port = 7077

4.           var webUiPort = 8080

5.           var propertiesFile: String = null

6.          

7.          // 读取启动脚本中设置的环境变量

8.           if(System.getenv("SPARK_MASTER_IP") != null) {

9.             logWarning("SPARK_MASTER_IP isdeprecated, please use SPARK_MASTER_HOST")

10.          host =System.getenv("SPARK_MASTER_IP")

11.        }

12.       

13.        if(System.getenv("SPARK_MASTER_HOST") != null) {

14.          host =System.getenv("SPARK_MASTER_HOST")

15.        }

16.        if(System.getenv("SPARK_MASTER_PORT") != null) {

17.          port =System.getenv("SPARK_MASTER_PORT").toInt

18.        }

19.        if(System.getenv("SPARK_MASTER_WEBUI_PORT") != null) {

20.          webUiPort =System.getenv("SPARK_MASTER_WEBUI_PORT").toInt

21.        }

22.       // 命令行选项参数的解析

23.        parse(args.toList)

24.       

25.        // This mutates the SparkConf, so allaccesses to it must be made after this line

26.        propertiesFile =Utils.loadDefaultSparkProperties(conf, propertiesFile)

27.       

28.        if(conf.contains("spark.master.ui.port")) {

29.          webUiPort =conf.get("spark.master.ui.port").toInt

30.        }

31.      ......

 

MasterArguments中的printUsageAndExit方法,对应的就是命令行中的帮助信息。

MasterArguments.scala源码:

1.           private defprintUsageAndExit(exitCode: Int) {

2.             // scalastyle:off println

3.             System.err.println(

4.               "Usage: Master [options]\n" +

5.               "\n" +

6.               "Options:\n" +

7.               " -i HOST, --ip HOST     Hostname tolisten on (deprecated, please use --host or -h) \n" +

8.               " -h HOST, --host HOST   Hostname tolisten on\n" +

9.               " -p PORT, --port PORT   Port tolisten on (default: 7077)\n" +

10.            " --webui-port PORT      Port forweb UI (default: 8080)\n" +

11.            " --properties-file FILE Path to a custom Spark properties file.\n" +

12.            "                         Default isconf/spark-defaults.conf.")

13.          // scalastyle:on println

14.          System.exit(exitCode)

15.        }

 

在解析完Master的参数之后,调用startRpcEnvAndEndpoin方法启动RPC通信环境以及Master的RPC通信终端。

Master.scala的startRpcEnvAndEndpoint源码如下:

1.           

2.          /**

3.            * 启动Master并返回一个三元组:

4.            *  (1) The Master RpcEnv

5.            *  (2) The web UI bound port

6.            *  (3) The REST server bound port, if any

7.            */

8.          

9.         def startRpcEnvAndEndpoint(

10.            host: String,

11.            port: Int,

12.            webUiPort: Int,

13.            conf: SparkConf): (RpcEnv, Int,Option[Int]) = {

14.          val securityMgr = new SecurityManager(conf)

15.         // 构建RPC通信环境

16.          val rpcEnv = RpcEnv.create(SYSTEM_NAME,host, port, conf, securityMgr)

17.         //构建RPC通信终端,实例化Master

18.          val masterEndpoint =rpcEnv.setupEndpoint(ENDPOINT_NAME,

19.            new Master(rpcEnv, rpcEnv.address,webUiPort, securityMgr, conf))

20.       

21.       // 向Master的通信终端发送请求,获取绑定的端口号

22.       // 包含Master的web ui监听端口号和REST的监听端口号

23.       

24.          val portsResponse =masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest)

25.          (rpcEnv, portsResponse.webUIPort,portsResponse.restPort)

26.        }

27.      }

 

startRpcEnvAndEndpoint方法中定义的ENDPOINT_NAME如下:

Master.scala源码:

1.           private[deploy] object Master extends Logging{

2.           val SYSTEM_NAME = "sparkMaster"

3.           val ENDPOINT_NAME = "Master"

4.         …….

 

startRpcEnvAndEndpoint方法中通过masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest)给Master自己本身发送一个消息BoundPortsRequest,是一个case object。发送消息BoundPortsRequest给自己确保masterEndpoint正常启动起来。返回消息的类型是BoundPortsResponse,是一个case class。

MasterMessages.scala源码:

1.          private[master] object MasterMessages {

2.         ……

3.           case object BoundPortsRequest

4.           case classBoundPortsResponse(rpcEndpointPort: Int, webUIPort: Int, restPort: Option[Int])

5.         }

 

Master收到消息BoundPortsRequest,发送返回消息BoundPortsResponse。

Master.scala源码:

1.          override def receiveAndReply(context:RpcCallContext): PartialFunction[Any, Unit] = {

2.         ......

3.         case BoundPortsRequest =>

4.               context.reply(BoundPortsResponse(address.port,webUi.boundPort, restServerBoundPort))

 

在BoundPortsResponse传入的参数restServerBoundPort是在Master的onStart方法中定义的:

Master.scala源码:

1.          ......

2.           private var restServerBoundPort: Option[Int]= None

3.          

4.           override def onStart(): Unit = {

5.         ......

6.            restServerBoundPort =restServer.map(_.start())

7.         …….

而在restServerBoundPort是通过restServer进行map操作启动赋值,看一下restServer。

Master.scala源码:

1.           private var restServer:Option[StandaloneRestServer] = None

2.         ......

3.            if (restServerEnabled) {

4.               val port =conf.getInt("spark.master.rest.port", 6066)

5.               restServer = Some(newStandaloneRestServer(address.host, port, conf, self, masterUrl))

6.             }

7.         ......

 

其中就new出来一个StandaloneRestServer。StandaloneRestServer服务器响应请求提交的[[RestSubmissionClient]]。将被嵌入到standalone Master中,仅用于集群模式。服务器根据不同的情况使用不同的HTTP代码进行响应:

l  200 OK -请求已成功处理

l  400错误请求:请求格式错误,未成功验证,或意外类型。

l  468未知协议版本:请求指定了此服务器不支持的协议。

l  500内部服务器错误:服务器在处理请求时引发内部异常

服务器在HTTP主体中总包含一个JSON表示的[[SubmitRestProtocolResponse]]。如果发生错误,服务器将包括一个[[ErrorResponse]]。如果构造了这个错误响应内部失败时,响应将由一个空体组成,响应体指示内部服务器错误。

StandaloneRestServer.scala源码:

1.          private[deploy] class StandaloneRestServer(

2.             host: String,

3.             requestedPort: Int,

4.             masterConf: SparkConf,

5.             masterEndpoint: RpcEndpointRef,

6.             masterUrl: String)

7.           extends RestSubmissionServer(host,requestedPort, masterConf) {

 

我们看一下RestSubmissionClient客户端。客户端提交申请[[RestSubmissionServer]]。在协议版本V1中, REST URL以表单形式出现http://[host:port]/v1/submissions/[action], [action]可以是create、kill、或状态的其中之一。每种请求类型都表示为发送到以下前缀的HTTP消息:

   (1) submit - POST to/submissions/create

   (2) kill - POST/submissions/kill/[submissionId]

   (3) status - GET/submissions/status/[submissionId]

在(1)的情况下,参数以JSON字段的形式发布到HTTP主体中。否则URL指定按客户端的预期操作。由于该协议预计将在Spark版本中保持稳定,因此现有字段不能添加或删除,但可以添加新的可选字段。如在少见的事件中向前或向后兼容性被破坏,Spark须引入一个新的协议版本(如V2)。客户机和服务器必须使用协议的同一版本进行通信。如果不匹配,服务器将用它支持的最高协议版本进行响应。此客户机的实现可以使用指定的版本使用该信息重试。

RestSubmissionClient.scala

1.          private[spark] classRestSubmissionClient(master: String) extends Logging {

2.           import RestSubmissionClient._

3.           private val supportedMasterPrefixes =Seq("spark://", "mesos://")

4.         ……

Restful把一切都看成是资源。利用Restful API可以对Spark进行监控。程序运行的每一个步骤、Task的计算步骤都可以可视化,对Spark的运行进行详细的监控。

 

 回到startRpcEnvAndEndpoint方法中,new创建了一个Master实例。Master实例化的时候会对所有的成员进行初始化。如默认的Cores个数等。  

Master.scala源码如下:

1.          private[deploy] class Master(

2.             override val rpcEnv: RpcEnv,

3.             address: RpcAddress,

4.             webUiPort: Int,

5.             val securityMgr: SecurityManager,

6.             val conf: SparkConf)

7.           extends ThreadSafeRpcEndpoint with Loggingwith LeaderElectable {

8.         …….

9.           // Default maxCores forapplications that don't specify it (i.e. pass Int.MaxValue)

10.        private val defaultCores =conf.getInt("spark.deploy.defaultCores", Int.MaxValue)

11.        val reverseProxy =conf.getBoolean("spark.ui.reverseProxy", false)

12.        if (defaultCores < 1) {

13.          throw newSparkException("spark.deploy.defaultCores must be positive")

14.        }

15.      ……

16.       

 

 Master继承了ThreadSafeRpcEndpoint和LeaderElectable,其中继承LeaderElectable涉及到Master的HA(High Availability ,高可用性)机制。这里先关注ThreadSafeRpcEndpoint,继承该类后,Master作为一个RpcEndpoint,实例化后首先会调用onStart方法。

Master.scala源码:

1.           override def onStart(): Unit = {

2.             logInfo("Starting Spark master at" + masterUrl)

3.             logInfo(s"Running Spark version${org.apache.spark.SPARK_VERSION}")

4.             // 构建一个Master的 web ui, 查看向Master提交的应用程序等信息

5.             webUi = new MasterWebUI(this, webUiPort)

6.             webUi.bind()

7.             masterWebUiUrl = "http://" +masterPublicAddress + ":" + webUi.boundPort

8.             if (reverseProxy) {

9.               masterWebUiUrl =conf.get("spark.ui.reverseProxyUrl", masterWebUiUrl)

10.            logInfo(s"Spark Master is acting asa reverse proxy. Master, Workers and " +

11.             s"Applications UIs are available at$masterWebUiUrl")

12.          }

13.        // 在一个守护线程中,启动调度机制,周期性检查Worker是否超时,当Worker节点超时后,会修改其状态或从Master中移除及其相关的操作

14.       

15.          checkForWorkerTimeOutTask =forwardMessageThread.scheduleAtFixedRate(new Runnable {

16.            override def run(): Unit =Utils.tryLogNonFatalError {

17.              self.send(CheckForWorkerTimeOut)

18.            }

19.          }, 0, WORKER_TIMEOUT_MS,TimeUnit.MILLISECONDS)

20.       

21.        // 默认情况下会启动Rest服务,可以通过该服务向Master提交各种请求

22.          if (restServerEnabled) {

23.            val port =conf.getInt("spark.master.rest.port", 6066)

24.            restServer = Some(newStandaloneRestServer(address.host, port, conf, self, masterUrl))

25.          }

26.          restServerBoundPort =restServer.map(_.start())

27.        // 度量(Metroics)相关的操作,用于监控

28.          masterMetricsSystem.registerSource(masterSource)

29.          masterMetricsSystem.start()

30.          applicationMetricsSystem.start()

31.         // 在度量系统启动后,将主程序和应用程序度量handler处理程序附加到Web UI中

32.          masterMetricsSystem.getServletHandlers.foreach(webUi.attachHandler)

33.          applicationMetricsSystem.getServletHandlers.foreach(webUi.attachHandler)

34.         //Master HA相关的操作

35.          val serializer = new JavaSerializer(conf)

36.          val (persistenceEngine_,leaderElectionAgent_) = RECOVERY_MODE match {

37.            case "ZOOKEEPER" =>

38.              logInfo("Persisting recovery stateto ZooKeeper")

39.              val zkFactory =

40.                newZooKeeperRecoveryModeFactory(conf, serializer)

41.              (zkFactory.createPersistenceEngine(),zkFactory.createLeaderElectionAgent(this))

42.            case "FILESYSTEM" =>

43.              val fsFactory =

44.                newFileSystemRecoveryModeFactory(conf, serializer)

45.              (fsFactory.createPersistenceEngine(),fsFactory.createLeaderElectionAgent(this))

46.            case "CUSTOM" =>

47.              val clazz =Utils.classForName(conf.get("spark.deploy.recoveryMode.factory"))

48.              val factory =clazz.getConstructor(classOf[SparkConf], classOf[Serializer])

49.                .newInstance(conf, serializer)

50.                .asInstanceOf[StandaloneRecoveryModeFactory]

51.              (factory.createPersistenceEngine(), factory.createLeaderElectionAgent(this))

52.            case _ =>

53.              (new BlackHolePersistenceEngine(), newMonarchyLeaderAgent(this))

54.          }

55.          persistenceEngine = persistenceEngine_

56.          leaderElectionAgent = leaderElectionAgent_

57.        }

 

其中在Master的onStart()方法中new出来MasterWebUI,启动一个webServder。

 Master.scala源码:

1.           override def onStart(): Unit = {

2.         ……  

3.          webUi = new MasterWebUI(this, webUiPort)

4.             webUi.bind()

5.             masterWebUiUrl = "http://" +masterPublicAddress + ":" + webUi.boundPort

 

如MasterWebUI 的spark.ui.killEnabled设置为true,可以通过WebUI页面可以把Spark的进程kill掉。

MasterWebUI.scala的源码:

1.            private[master]

2.         class MasterWebUI(

3.             val master: Master,

4.             requestedPort: Int)

5.           extends WebUI(master.securityMgr,master.securityMgr.getSSLOptions("standalone"),

6.             requestedPort, master.conf, name ="MasterUI") with Logging {

7.           val masterEndpointRef = master.self

8.           val killEnabled =master.conf.getBoolean("spark.ui.killEnabled", true)

9.         …….

10.      initialize()

11.       

12.        /** Initialize all components of the server.*/

13.        def initialize() {

14.          val masterPage = new MasterPage(this)

15.          attachPage(new ApplicationPage(this))

16.          attachPage(masterPage)

17.          attachHandler(createStaticHandler(MasterWebUI.STATIC_RESOURCE_DIR,"/static"))

18.          attachHandler(createRedirectHandler(

19.            "/app/kill", "/",masterPage.handleAppKillRequest, httpMethods = Set("POST")))

20.          attachHandler(createRedirectHandler(

21.            "/driver/kill", "/",masterPage.handleDriverKillRequest, httpMethods = Set("POST")))

22.        }

 

MasterWebUI中在初始化的时候new出来MasterPage,在MasterPage中通过代码去写Web页面。

MasterPage.scala源码如下:

1.          private[ui] class MasterPage(parent:MasterWebUI) extends WebUIPage("") {

2.          ......

3.           override def renderJson(request:HttpServletRequest): JValue = {

4.             JsonProtocol.writeMasterState(getMasterState)

5.           }

6.         .....

7.           val content =

8.                 

9.                   

10.                  

11.                    

  • URL:{state.uri}
  • 12.                    {

    13.                      state.restUri.map { uri =>

    14.                        

  • 15.                          RESTURL: {uri}

    16.                           (cluster mode)

    17.                        

  • 18.                      }.getOrElse { Seq.empty }

    19.                    }

    20.                    

  • AliveWorkers: {aliveWorkers.length}
  • 21.                    

  • Cores inuse: {aliveWorkers.map(_.cores).sum} Total,

    22.                      {aliveWorkers.map(_.coresUsed).sum}Used

  • 23.                    

  • Memory inuse:

    24.                      {Utils.megabytesToString(aliveWorkers.map(_.memory).sum)}Total,

    25.                      {Utils.megabytesToString(aliveWorkers.map(_.memoryUsed).sum)}Used

  • 26.                    

  • Applications:

    27.                      {state.activeApps.length} Running,

    28.                      {state.completedApps.length}Completed

  • 29.                    

  • Drivers:

    30.                      {state.activeDrivers.length}Running,

    31.                      {state.completedDrivers.length}Completed

  • 32.                    

  • Status:{state.status}
  • 33.                  

    34.                

    35.              

    36.      ........

     

    回到MasterWebUI.scala的initialize()方法,其中调用了attachPage方法,在WebUI中增加Web页面。

    WebUI.scala源码:

    1.             def attachPage(page: WebUIPage) {

    2.             val pagePath = "/" + page.prefix

    3.             val renderHandler =createServletHandler(pagePath,

    4.               (request: HttpServletRequest) =>page.render(request), securityManager, conf, basePath)

    5.             val renderJsonHandler =createServletHandler(pagePath.stripSuffix("/") + "/json",

    6.               (request: HttpServletRequest) =>page.renderJson(request), securityManager, conf, basePath)

    7.             attachHandler(renderHandler)

    8.             attachHandler(renderJsonHandler)

    9.             val handlers =pageToHandlers.getOrElseUpdate(page, ArrayBuffer[ServletContextHandler]())

    10.          handlers += renderHandler

    11.        }

     

    在WebUI的bind方法中启用了JettyServer。

    WebUI.scala的bind源码:

    1.           def bind() {

    2.             assert(!serverInfo.isDefined,s"Attempted to bind $className more than once!")

    3.             try {

    4.               val host = Option(conf.getenv("SPARK_LOCAL_IP")).getOrElse("0.0.0.0")

    5.               serverInfo = Some(startJettyServer(host,port, sslOptions, handlers, conf, name))

    6.               logInfo(s"Bound $className to $host,and started at $webUrl")

    7.             } catch {

    8.               case e: Exception =>

    9.                 logError(s"Failed to bind$className", e)

    10.              System.exit(1)

    11.          }

    12.        }

     

    JettyUtils .scala的startJettyServer尝试Jetty服务器绑定到所提供的主机名、端口。

    startJettyServer源码如下:

    1.         def startJettyServer(

    2.               hostName: String,

    3.               port: Int,

    4.               sslOptions: SSLOptions,

    5.               handlers: Seq[ServletContextHandler],

    6.               conf: SparkConf,

    7.               serverName: String = ""):ServerInfo = { 

     

     

    你可能感兴趣的:(SparkInBeiJing)