【面試必問】spark源碼剖析之Standalone worker啓動流程

點個關注吧,球球啦!
spark源碼剖析相關:
spark Standalone master啓動流程 https://blog.csdn.net/Mr_kidBK/article/details/105131444
Standalone worker啓動流程 https://blog.csdn.net/Mr_kidBK/article/details/105356632

前言

      本文章是對standalone方式啓動spark的源碼角度說明從執行shell腳本到worker啓動主要步驟,spark版本2.1.x,信息來源爲spark官網和源碼,如果與網上其他文章有較大的偏差,建議以我爲準!

啓動腳本

運行start-all.sh時會依次調用start-slaves.sh->slave.sh
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在要啓動worker的機器上執行spark-slave.shspark-daemon.sh ->spark-class
最終在目標機器上執行:

/opt/module/jdk1.8.0/bin/java -cp /opt/module/spark-standalone/conf/:/opt/module/spark-standalone/jars/* -Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=dai102:2181,dai103:2181,dai104:2181 -Dspark.deploy.zookeeper.dir=/spark_dai -Xmx1g org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://dai102:7077

建議配合master啓動流程一起食用

spark Standalone master啓動流程
https://blog.csdn.net/Mr_kidBK/article/details/105131444

Worker 類

main方法
  1. 構建解析參數的實例 (與master過程相同,在此不再進行過多贅述)
  2. 啓動 Rpc 環境和 Rpc 終端
    def main(argStrings: Array[String]) {
        Utils.initDaemon(log)
        val conf = new SparkConf
        // 構建解析參數的實例
        val args = new WorkerArguments(argStrings, conf)
        // 啓動 Rpc 環境和 Rpc 終端
        val rpcEnv = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores,
            args.memory, args.masters, args.workDir, conf = conf)
        rpcEnv.awaitTermination()
    }

我們都知道nettyrpc在一個生命週期中會先被調用構造方法和onstart方法

構造方法沒什麼可看的,都是一些配置。
主要是onstart方法

onstart方法
  1. 檢查 Worker 是否未註冊
  2. 創建工作目錄
  3. 啓動 shuffle 服務
  4. 創建 Worker UI
  5. 向 Master 註冊 Worker (核心邏輯)
    override def onStart() {
        // 第一次啓動要求 Worker 未註冊
        assert(!registered)
        logInfo("Starting Spark worker %s:%d with %d cores, %s RAM".format(
            host, port, cores, Utils.megabytesToString(memory)))
        logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")
        logInfo("Spark home: " + sparkHome)
        // 創建工作目錄
        createWorkDir()
        // 如果可以用, 則啓動 shuffle 服務
        shuffleService.startIfEnabled()
        // Worker的 WebUI(master 8080  worker 8081  job 4040)
        webUi = new WorkerWebUI(this, workDir, webUiPort)
        webUi.bind()
        
        workerWebUiUrl = s"http://$publicAddress:${webUi.boundPort}"
        // 向 Master 註冊 Worker (核心邏輯) ->
        registerWithMaster()
        
        metricsSystem.registerSource(workerSource)
        metricsSystem.start()
        // Attach the worker metrics servlet handler to the web ui after the metrics system is started.
        metricsSystem.getServletHandlers.foreach(webUi.attachHandler)
    }

向master註冊調用了registerWithMaster方法

registrationRetryTimer的默認值爲none

private var registrationRetryTimer: Option[JScheduledFuture[_]] = None
  1. 嘗試向所有的 master 註冊
  2. 爲避免註冊失敗以固定頻率再嘗試向所有的 master 註冊
    /**
     * 向 Master 註冊 Worker
     */
    private def registerWithMaster() {
        // onDisconnected may be triggered multiple times, so don't attempt registration
        // if there are outstanding registration attempts scheduled.
        registrationRetryTimer match {
            case None =>
                registered = false
                // 向所有的 Master 註冊
                registerMasterFutures = tryRegisterAllMasters()
                connectionAttemptCount = 0
                // 前面有可能註冊失敗, 後面再以固定的品向 master 註冊
                registrationRetryTimer = Some(forwordMessageScheduler.scheduleAtFixedRate(
                    new Runnable {
                        override def run(): Unit = Utils.tryLogNonFatalError {
                            Option(self).foreach(_.send(ReregisterWithMaster))
                        }
                    },
                    // [0.5, 1.5) * 10
                    INITIAL_REGISTRATION_RETRY_INTERVAL_SECONDS,
                    INITIAL_REGISTRATION_RETRY_INTERVAL_SECONDS,
                    TimeUnit.SECONDS))
            case Some(_) =>
                logInfo("Not spawning another attempt to register with the master, since there is an" +
                    " attempt scheduled already.")
        }
    }
  1. 從線程池中啓動線程來執行 Worker 向 Master 註冊
  2. 向master發送RPC ask請求
// 嘗試向所有的master註冊
    private def tryRegisterAllMasters(): Array[JFuture[_]] = {
        masterRpcAddresses.map { masterAddress =>
            // 從線程池中啓動線程來執行 Worker 向 Master 註冊
            registerMasterThreadPool.submit(new Runnable {
                override def run(): Unit = {
                    try {
                        logInfo("Connecting to master " + masterAddress + "...")
                        // 根據 Master 的地址得到一個 Master 的 RpcEndpointRef, 然後就可以和 Master 進行通訊了.
                        val masterEndpoint: RpcEndpointRef = rpcEnv.setupEndpointRef(masterAddress, Master.ENDPOINT_NAME)
                        // 向 Master 註冊
                        registerWithMaster(masterEndpoint)
                    } catch {
                        case ie: InterruptedException => // Cancelled
                        case NonFatal(e) => logWarning(s"Failed to connect to master $masterAddress", e)
                    }
                }
            })
        }
    }
    private def registerWithMaster(masterEndpoint: RpcEndpointRef): Unit = {
        // 向 Master 對應的 receiveAndReply 方法發送信息
        // 信息的類型是 RegisterWorker, 包括 Worker 的一些信息: id, 主機地址, 端口號, 內存, webUi
        // ask: 發送信息的時候, 要求對方有迴應
        masterEndpoint.ask[RegisterWorkerResponse](RegisterWorker(
            workerId, host, port, self, cores, memory, workerWebUiUrl))
            .onComplete {
                // This is a very fast action so we can use "ThreadUtils.sameThread"
                case Success(msg) =>
                    Utils.tryLogNonFatalError {
                        handleRegisterResponse(msg)
                    }
                case Failure(e) =>
                    logError(s"Cannot register with master: ${masterEndpoint.address}", e)
                    System.exit(1)
            }(ThreadUtils.sameThread)
    }

Master收到請求後,會先判斷該worker是否已經註冊,如果已經註冊但是worker是dead狀態,刪除worker信息,重新註冊。

Master會有三個地方存儲worker的相關信息,一個HashSet,兩個HashMap(一個id映射,一個地址映射)
在這裏插入圖片描述
然後master會將自己的ref,UIurl作爲返回值返回給worker
在這裏插入圖片描述

worker收到返回信息後,首先會慶祝自己註冊成功,先打個日誌,隨便吧自己的註冊狀態改成true
然後通知自己每隔15秒給這個B發送心跳信息
在這裏插入圖片描述
Master收到正常的信息後會更新心跳(超過4個心跳沒收到會被標記爲超時狀態)
Master如果收到不正常的心跳,先會抱怨一句“你發你馬呢”,打印一段日誌,並且反向問候對方,確認對方健在。
在這裏插入圖片描述

private def handleRegisterResponse(msg: RegisterWorkerResponse): Unit = synchronized {
        msg match {
            case RegisteredWorker(masterRef, masterWebUiUrl) =>
                logInfo("Successfully registered with master " + masterRef.address.toSparkURL)
                // 已經註冊過了
                registered = true
                // 更新 Master
                changeMaster(masterRef, masterWebUiUrl)
                // 通知自己給 Master 發送心跳信息  默認 1 分鐘 4 次
                forwordMessageScheduler.scheduleAtFixedRate(new Runnable {
                    override def run(): Unit = Utils.tryLogNonFatalError {
                        self.send(SendHeartbeat)
                    }
                }, 0, HEARTBEAT_MILLIS, TimeUnit.MILLISECONDS)
                if (CLEANUP_ENABLED) {
                    logInfo(
                        s"Worker cleanup enabled; old application directories will be deleted in: $workDir")
                    forwordMessageScheduler.scheduleAtFixedRate(new Runnable {
                        override def run(): Unit = Utils.tryLogNonFatalError {
                            self.send(WorkDirCleanup)
                        }
                    }, CLEANUP_INTERVAL_MILLIS, CLEANUP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
                }
                
                val execs = executors.values.map { e =>
                    new ExecutorDescription(e.appId, e.execId, e.cores, e.state)
                }
                masterRef.send(WorkerLatestState(workerId, execs.toList, drivers.keys.toSeq))
            
            case RegisterWorkerFailed(message) =>
                if (!registered) {
                    logError("Worker registration failed: " + message)
                    System.exit(1)
                }
            
            case MasterInStandby =>
            // Ignore. Master not yet ready.
        }
    }

點個贊再走,球球啦!

原創不易,白嫖不好,各位的支持和認可,就是我創作的最大動力,我們下篇文章見!

本博客僅發佈於CSDN—一個帥到不能再帥的人 Mr_kidBK。轉載請標明出處。
https://blog.csdn.net/Mr_kidBK

點贊!收藏!轉發!!!麼麼噠!
點贊!收藏!轉發!!!麼麼噠!
點贊!收藏!轉發!!!麼麼噠!
點贊!收藏!轉發!!!麼麼噠!
點贊!收藏!轉發!!!麼麼噠!
————————————————
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章