Spark源码解析-Master的启动流程

偶尔善良 提交于 2020-02-08 05:25:14


大致的整体启动流程如下:
在这里插入图片描述

1.main方法

在StandAlone模式下,Spark集群中存在两种重要的节点,分别是Master和Worker。那么作为集群管理器的Master到底是怎样启动的呢?我们通过源码来查看一下。

在运行了start-all.sh脚本之后,最终Master类会启动(至于脚本的启动过程在此省略)。
下面是Master的伴生对象的main方法:

// 启动 Master 的入口函数
    def main(argStrings: Array[String]) {
        Utils.initDaemon(log)
        val conf = new SparkConf
        // 构建用于参数解析的实例   --host hadoop201 --port 7077 --webui-port 8080
        val args: MasterArguments = new MasterArguments(argStrings, conf)
        // 启动 RPC 通信环境和 MasterEndPoint
        val (rpcEnv, _, _): (RpcEnv, Int, Option[Int]) = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, conf)
        // 防止master进程退出
        rpcEnv.awaitTermination()
    }

可以看出:main方法中主要是对启动Master类时穿阿迪进来的参数封装成MasterArguments;然后最重要的是调用startRpcEnvAndEndpoint()方法来创建Netty通信环境和MasterEndpoint。

1.1 startRpcEnvAndEndpoint()

那么startRpcEnvAndEndpoint()是如何创建的呢?可以查看以下源码:

def startRpcEnvAndEndpoint(
                                  host: String,
                                  port: Int,
                                  webUiPort: Int,
                                  conf: SparkConf): (RpcEnv, Int, Option[Int]) = {
        // 安全管理器, 主要对账号, 权限及身份认证进行设置和管理
        val securityMgr = new SecurityManager(conf)
        // 创建 Master 端的 RpcEnv 环境, 并启动 RpcEnv
        // 参数: sparkMaster hadoop201 7077 conf securityMgr
        // 返回值  的实际类型是: NettyRpcEnv
        val rpcEnv: RpcEnv = RpcEnv.create(SYSTEM_NAME, host, port, conf, securityMgr)  // ->
        // 创建 Master对象, 该对象就是一个 RpcEndpoint, 在 RpcEnv 中注册这个 RpcEndpoint
        // 返回该 RpcEndpoint 的引用(master 的RpcEndpointRef)
        val masterEndpoint: RpcEndpointRef = rpcEnv.setupEndpoint(ENDPOINT_NAME,
            new Master(rpcEnv, rpcEnv.address, webUiPort, securityMgr, conf)) //->
        // 向 Master 的通信终端发法请求,获取 BoundPortsResponse 对象
        // BoundPortsResponse 是一个样例类包含三个属性: rpcEndpointPort webUIPort restPort
        val portsResponse: BoundPortsResponse = masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest)
        (rpcEnv, portsResponse.webUIPort, portsResponse.restPort)
    }

可以看到在其中主要是调用了两个方法:RpcEnv.create()方法和rpcEnv.setupEndpoint()方法。
那么接下来我们就从这两个方法入手开始分析。

1.1.1 create

private[spark] object RpcEnv {

    def create(
                  name: String,
                  host: String,
                  port: Int,
                  conf: SparkConf,
                  securityManager: SecurityManager,
                  clientMode: Boolean = false): RpcEnv = {
        create(name, host, host, port, conf, securitanager, clientMode)
    }
    // 创建RpcEnv
    def create(
                  name: String,
                  bindAddress: String,
                  advertiseAddress: String,
                  port: Int,
                  conf: SparkConf,
                  securityManager: SecurityManager,
                  clientMode: Boolean): RpcEnv = {
        // 保存 RpcEnv 的配置信息  ->
        val config = RpcEnvConfig(conf, name, bindAddress, advertiseAddress, port, securityManager,
            clientMode)
        // 创建 NettyRpcEvn
        new NettyRpcEnvFactory().create(config) // create ->
    }
}

可见最终调用的是NettyRpcEnvFactory对象的create方法,再次查看源码:

def create(config: RpcEnvConfig): RpcEnv = {
        val sparkConf: 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
        // 用于 Rpc 传输对象时的序列化
        val javaSerializerInstance: JavaSerializerInstance = new JavaSerializer(sparkConf)
            .newInstance()
            .asInstanceOf[JavaSerializerInstance]
        // 实例化 NettyRpcEnv
        val nettyEnv = new NettyRpcEnv(
            sparkConf,
            javaSerializerInstance,
            config.advertiseAddress,
            config.securityManager)
        if (!config.clientMode) {
            // 2. 定义 NettyRpcEnv 的启动函数
            val startNettyRpcEnv: Int => (NettyRpcEnv, Int) = { actualPort =>
                
                nettyEnv.startServer(config.bindAddress, actualPort)
                (nettyEnv, nettyEnv.address.port)
            }
            try {
                // 1. 启动 NettyRpcEnv   使用Utils工具类: Utils.startServiceOnPort
                Utils.startServiceOnPort(config.port, startNettyRpcEnv, sparkConf, config.name)._1
            } catch {
                case NonFatal(e) =>
                    nettyEnv.shutdown()
                    throw e
            }
        }
        nettyEnv
    }

可见该方法的作用有四个:
(1)创建Rpc对象传输时的序列化器
(2)实例化NettyRpcEnv
(3)startServer方法启动NettyRpcEnv
(4)最终返回NettyRpcEnv

1.1.2 setupEndpoint()

那么接下来我们看一下setupEndpoint()方法:

val masterEndpoint: RpcEndpointRef = rpcEnv.setupEndpoint(ENDPOINT_NAME,
            new Master(rpcEnv, rpcEnv.address, webUiPort, securityMgr, conf))

它的作用主要有两个:
(1)创建 Master对象, 该对象就是一个 RpcEndpoint, 在 RpcEnv 中注册这个 RpcEndpoint
(2)返回该 RpcEndpoint 的引用(master 的RpcEndpointRef)

该方法调用的是RpcEnv的唯一的实现类NettyRpcEnv中的setupEndpoint()方法:

override def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef = {
        dispatcher.registerRpcEndpoint(name, endpoint)
    }

可见其作用主要是在dispatcher中注册RpcEndpoint,然后返回端点的引用RpcEndpointRef。

1.2 Constructor

可以注意到在setupEndpoint()方法中创建了一个Master对象。
那么Master在创建的时候会做出哪些初始化呢?

private val WORKER_TIMEOUT_MS = conf.getLong("spark.worker.timeout", 60) * 1000
    private val RETAINED_APPLICATIONS = conf.getInt("spark.deploy.retainedApplications", 200)
    private val RETAINED_DRIVERS = conf.getInt("spark.deploy.retainedDrivers", 200)
    private val REAPER_ITERATIONS = conf.getInt("spark.dead.worker.persistence", 15)
    private val RECOVERY_MODE = conf.get("spark.deploy.recoveryMode", "NONE")
    private val MAX_EXECUTOR_RETRIES = conf.getInt("spark.deploy.maxExecutorRetries", 10)
    
    val workers = new HashSet[WorkerInfo]
    val idToApp = new HashMap[String, ApplicationInfo]
    private val waitingApps = new ArrayBuffer[ApplicationInfo]
    val apps = new HashSet[ApplicationInfo]
    // id -> wokerInfo
    private val idToWorker = new HashMap[String, WorkerInfo]
    private val addressToWorker = new HashMap[RpcAddress, WorkerInfo]
    
    private val endpointToApp = new HashMap[RpcEndpointRef, ApplicationInfo]
    private val addressToApp = new HashMap[RpcAddress, ApplicationInfo]
    private val completedApps = new ArrayBuffer[ApplicationInfo]
    private var nextAppNumber = 0
    
    private val drivers = new HashSet[DriverInfo]
    private val completedDrivers = new ArrayBuffer[DriverInfo]
    // Drivers currently spooled for scheduling
    private val waitingDrivers = new ArrayBuffer[DriverInfo]
    private var nextDriverNumber = 0

主要是保存和查找WorkerInfo的HashSet和HashMap是比较重要的。
而Master自身其实也是一个RpvEndpoint:

private[deploy] class Master(
                                override val rpcEnv: RpcEnv,
                                address: RpcAddress,
                                webUiPort: Int,
                                val securityMgr: SecurityManager,
                                val conf: SparkConf)
    extends ThreadSafeRpcEndpoint with Logging with LeaderElectable {......}

2. onStart()方法

在RpcEndpoint特质中有这样的描述

/**
 * An end point for the RPC that defines what functions to trigger given a message.
 *
 * It is guaranteed that `onStart`, `receive` and `onStop` will be called in sequence.
 *
 * The life-cycle of an endpoint is:
 *
 * constructor -> onStart -> receive* -> onStop
 */
private[spark] trait RpcEndpoint {}

可以知道一个Endpoint的生命周期方法是:
constructor -> onStart -> receive* -> onStop 按照这样的顺序执行的。
那么我们再来看一下Master的onStart()方法:
核心代码如下:

override def onStart(): Unit = {
        logInfo("Starting Spark master at " + masterUrl)
        logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")
        // 1. 创建 WebUI 服务器
        webUi = new MasterWebUI(this, webUiPort)
        webUi.bind()
        masterWebUiUrl = "http://" + masterPublicAddress + ":" + webUi.boundPort
        if (reverseProxy) {
            masterWebUiUrl = conf.get("spark.ui.reverseProxyUrl", masterWebUiUrl)
            logInfo(s"Spark Master is acting as a reverse proxy. Master, Workers and " +
                s"Applications UIs are available at $masterWebUiUrl")
        }
        // 2. 按照固定的频率去启动线程来检查 Worker 是否超时. 其实就是给自己发信息: CheckForWorkerTimeOut
        // 默认是每分钟检查一次.
        checkForWorkerTimeOutTask = forwardMessageThread.scheduleAtFixedRate(new Runnable {
            override def run(): Unit = Utils.tryLogNonFatalError {
                self.send(CheckForWorkerTimeOut)  // 自己给自己发送信息
            }
        }, 0, WORKER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
        ......
}

可以看出主要有两个作用:
(1)master WebUi 服务器
(2)周期性的检测work是否超时(每分钟检查一次)

在self.send(CheckForWorkerTimeOut)之后,Master会自己接收信息,在receive方法中:

override def receive: PartialFunction[Any, Unit] = {
	......
	case CheckForWorkerTimeOut =>
            // 移除超时的workers
            timeOutDeadWorkers()
}

查看timeOutDeadWorkers()方法:

/** Check for, and remove, any timed-out workers */
    private def timeOutDeadWorkers() {
        // Copy the workers into an array so we don't modify the hashset while iterating through it
        val currentTime = System.currentTimeMillis()
        //  把超时的 Worker 从 workers 中移除
        val toRemove: Array[WorkerInfo] = workers.filter(_.lastHeartbeat < currentTime - WORKER_TIMEOUT_MS).toArray
        for (worker <- toRemove) {
            // 如果 worker 的状态不是 DEAD
            if (worker.state != WorkerState.DEAD) {
                logWarning("Removing %s because we got no heartbeat in %d seconds".format(
                    worker.id, WORKER_TIMEOUT_MS / 1000))
                // 移除超时的worker
                removeWorker(worker) // ->
            } else {
                // 移除worker的尸体
                if (worker.lastHeartbeat < currentTime - ((REAPER_ITERATIONS + 1) * WORKER_TIMEOUT_MS)) {
                    workers -= worker // we've seen this DEAD worker in the UI, etc. for long enough; cull it
                }
            }
        }
    }

至此Master的启动流程分析完毕。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!