深入理解Spark 2.1 Core (五):Standalone模式
概述
前幾篇博文都在介紹Spark的排程,這篇博文我們從更加宏觀的排程看Spark,講講Spark的部署模式。Spark部署模式分以下幾種:
local 模式
local-cluster 模式
Standalone 模式
YARN 模式
Mesos 模式
我們先來簡單介紹下YARN模式,然後深入講解Standalone模式。
YARN 模式介紹
YARN介紹
YARN是一個資源管理、任務排程的框架,主要包含三大模組:ResourceManager(RM)、NodeManager(NM)、ApplicationMaster(AM)。
其中,ResourceManager負責所有資源的監控、分配和管理;ApplicationMaster負責每一個具體應用程式的排程和協調;NodeManager負責每一個節點的維護。
對於所有的applications,RM擁有絕對的控制權和對資源的分配權。而每個AM則會和RM協商資源,同時和NodeManager通訊來執行和監控task。幾個模組之間的關係如圖所示。
這裡寫圖片描述
Yarn Cluster 模式
這裡寫圖片描述
Spark的Yarn Cluster 模式流程如下:
本地用YARN Client 提交App 到 Yarn Resource Manager
-
Yarn Resource Manager 選個 YARN Node Manager,用它來
建立個ApplicationMaster,SparkContext相當於是這個ApplicationMaster管的APP,生成YarnClusterScheduler與YarnClusterSchedulerBackend
選擇叢集中的容器啟動CoarseCrainedExecutorBackend,用來啟動spark.executor。
ApplicationMaster與CoarseCrainedExecutorBackend會有遠端呼叫。
Yarn Client 模式
這裡寫圖片描述
Spark的Yarn Client 模式流程如下:
本地啟動SparkContext,生成YarnClientClusterScheduler 和 YarnClientClusterSchedulerBackend
YarnClientClusterSchedulerBackend啟動yarn.Client,用它提交App 到 Yarn Resource Manager
Yarn Resource Manager 選個 YARN Node Manager,用它來選擇叢集中的容器啟動CoarseCrainedExecutorBackend,用來啟動spark.executor
YarnClientClusterSchedulerBackend與CoarseCrainedExecutorBackend會有遠端呼叫。
Standalone 模式介紹
這裡寫圖片描述
啟動app,在SparkContxt啟動過程中,先初始化DAGScheduler 和 TaskScheduler,並初始化 SparkDeploySchedulerBackend,並在其內部啟動DriverEndpoint和ClientEndpoint。
ClientEndpoint想Master註冊app,Master收到註冊資訊後把該app加入到等待執行app列表中,等待由Master分配給該app worker。
app獲取到worker後,Master通知Worker的WorkerEndpont建立CoarseGrainedExecutorBackend程式,在該程式中建立執行容器executor
executor建立完畢後傳送資訊給Master和DriverEndpoint,告知Executor建立完畢,在SparkContext註冊,後等待DriverEndpoint傳送執行任務的訊息。
SparkContext分配TaskSet給CoarseGrainedExecutorBackend,按一定排程策略在executor執行。詳見:與
CoarseGrainedExecutorBackend在Task處理的過程中,把處理Task的狀態傳送給DriverEndpoint,Spark根據不同的執行結果來處理。若處理完畢,則繼續傳送其他TaskSet。詳見:
app執行完成後,SparkContext會進行資源回收,銷燬Worker的CoarseGrainedExecutorBackend程式,然後登出自己。
Standalone 啟動叢集
這裡寫圖片描述
啟動Master
master.Master
我們先來看下Master物件的main函式做了什麼:
private[deploy] object Master extends Logging { val SYSTEM_NAME = "sparkMaster" val ENDPOINT_NAME = "Master" def main(argStrings: Array[String]) { Utils.initDaemon(log) //建立SparkConf val conf = new SparkConf //解析SparkConf引數 val args = new MasterArguments(argStrings, conf) val (rpcEnv, _, _) = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, conf) rpcEnv.awaitTermination() } def startRpcEnvAndEndpoint( host: String, port: Int, webUiPort: Int, conf: SparkConf): (RpcEnv, Int, Option[Int]) = { val securityMgr = new SecurityManager(conf) val rpcEnv = RpcEnv.create(SYSTEM_NAME, host, port, conf, securityMgr) //建立Master val masterEndpoint = rpcEnv.setupEndpoint(ENDPOINT_NAME, new Master(rpcEnv, rpcEnv.address, webUiPort, securityMgr, conf)) val portsResponse = masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest) //返回 Master RpcEnv, //web UI 埠, //其他服務的埠 (rpcEnv, portsResponse.webUIPort, portsResponse.restPort) } }
master.MasterArguments
接下來我們看看master是如何解析引數的:
private[master] class MasterArguments(args: Array[String], conf: SparkConf) extends Logging { //預設配置 var host = Utils.localHostName() var port = 7077 var webUiPort = 8080 //Spark屬性檔案 //預設為 spark-default.conf var propertiesFile: String = null // 檢查環境變數 if (System.getenv("SPARK_MASTER_IP") != null) { logWarning("SPARK_MASTER_IP is deprecated, please use SPARK_MASTER_HOST") host = System.getenv("SPARK_MASTER_IP") } if (System.getenv("SPARK_MASTER_HOST") != null) { host = System.getenv("SPARK_MASTER_HOST") } if (System.getenv("SPARK_MASTER_PORT") != null) { port = System.getenv("SPARK_MASTER_PORT").toInt } if (System.getenv("SPARK_MASTER_WEBUI_PORT") != null) { webUiPort = System.getenv("SPARK_MASTER_WEBUI_PORT").toInt } parse(args.toList) // 轉變SparkConf propertiesFile = Utils.loadDefaultSparkProperties(conf, propertiesFile) //環境變數的SPARK_MASTER_WEBUI_PORT //會被Spark屬性spark.master.ui.port所覆蓋 if (conf.contains("spark.master.ui.port")) { webUiPort = conf.get("spark.master.ui.port").toInt } //解析命令列引數 //命令列引數會把環境變數和Spark屬性都覆蓋 @tailrec private def parse(args: List[String]): Unit = args match { case ("--ip" | "-i") :: value :: tail => Utils.checkHost(value, "ip no longer supported, please use hostname " + value) host = value parse(tail) case ("--host" | "-h") :: value :: tail => Utils.checkHost(value, "Please use hostname " + value) host = value parse(tail) case ("--port" | "-p") :: IntParam(value) :: tail => port = value parse(tail) case "--webui-port" :: IntParam(value) :: tail => webUiPort = value parse(tail) case ("--properties-file") :: value :: tail => propertiesFile = value parse(tail) case ("--help") :: tail => printUsageAndExit(0) case Nil => case _ => printUsageAndExit(1) } private def printUsageAndExit(exitCode: Int) { System.err.println( "Usage: Master [options]n" + "n" + "Options:n" + " -i HOST, --ip HOST Hostname to listen on (deprecated, please use --host or -h) n" + " -h HOST, --host HOST Hostname to listen onn" + " -p PORT, --port PORT Port to listen on (default: 7077)n" + " --webui-port PORT Port for web UI (default: 8080)n" + " --properties-file FILE Path to a custom Spark properties file.n" + " Default is conf/spark-defaults.conf.") System.exit(exitCode) } }
我們可以看到上述引數設定的優先順序別為:
$large系統環境變數 < spark-default.conf中的屬性 < 命令列引數 < 應用級程式碼中的引數設定$
啟動Worker
worker.Worker
我們先來看下Worker物件的main函式做了什麼:
private[deploy] object Worker extends Logging { val SYSTEM_NAME = "sparkWorker" val ENDPOINT_NAME = "Worker" def main(argStrings: Array[String]) { Utils.initDaemon(log) //建立SparkConf val conf = new SparkConf //解析SparkConf引數 val args = new WorkerArguments(argStrings, conf) val rpcEnv = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores, args.memory, args.masters, args.workDir, conf = conf) rpcEnv.awaitTermination() } 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 = { val systemName = SYSTEM_NAME + workerNumber.map(_.toString).getOrElse("") val securityMgr = new SecurityManager(conf) val rpcEnv = RpcEnv.create(systemName, host, port, conf, securityMgr) val masterAddresses = masterUrls.map(RpcAddress.fromSparkURL(_)) //建立Worker rpcEnv.setupEndpoint(ENDPOINT_NAME, new Worker(rpcEnv, webUiPort, cores, memory, masterAddresses, ENDPOINT_NAME, workDir, conf, securityMgr)) rpcEnv } ***
worker.WorkerArguments
worker.WorkerArguments與master.MasterArguments類似:
private[worker] class WorkerArguments(args: Array[String], conf: SparkConf) { var host = Utils.localHostName() var port = 0 var webUiPort = 8081 var cores = inferDefaultCores() var memory = inferDefaultMemory() var masters: Array[String] = null var workDir: String = null var propertiesFile: String = null // 檢查環境變數 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) // 轉變SparkConf propertiesFile = Utils.loadDefaultSparkProperties(conf, propertiesFile) if (conf.contains("spark.worker.ui.port")) { webUiPort = conf.get("spark.worker.ui.port").toInt } checkWorkerMemory() @tailrec private def parse(args: List[String]): Unit = args match { case ("--ip" | "-i") :: value :: tail => Utils.checkHost(value, "ip no longer supported, please use hostname " + value) host = value parse(tail) case ("--host" | "-h") :: value :: tail => Utils.checkHost(value, "Please use hostname " + 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) case "--webui-port" :: IntParam(value) :: tail => webUiPort = value parse(tail) case ("--properties-file") :: value :: tail => propertiesFile = value parse(tail) case ("--help") :: tail => printUsageAndExit(0) case value :: tail => if (masters != null) { // Two positional arguments were given printUsageAndExit(1) } masters = Utils.parseStandaloneMasterUrls(value) parse(tail) case Nil => if (masters == null) { // No positional argument was given printUsageAndExit(1) } case _ => printUsageAndExit(1) } ***
資源回收
我們在概述中提到了“ app執行完成後,SparkContext會進行資源回收,銷燬Worker的CoarseGrainedExecutorBackend程式,然後登出自己。”接下來我們就來講解下Master和Executor是如何感知到Application的退出的。
呼叫棧如下:
SparkContext.stop
-
DAGScheduler.stop
CoarseGrainedSchedulerBackend.stop
CoarseGrainedSchedulerBackend.DriverEndpoint.receiveAndReply
CoarseGrainedSchedulerBackend.DriverEndpoint.receiveAndReply
Executor.stop
CoarseGrainedExecutorBackend.receive
CoarseGrainedSchedulerBackend.stopExecutors
TaskSchedulerImpl.stop
SparkContext.stop
SparkContext.stop會呼叫DAGScheduler.stop
*** if (_dagScheduler != null) { Utils.tryLogNonFatalError { _dagScheduler.stop() } _dagScheduler = null } ***
DAGScheduler.stop
DAGScheduler.stop會呼叫TaskSchedulerImpl.stop
def stop() { //停止訊息排程 messageScheduler.shutdownNow() //停止事件處理迴圈 eventProcessLoop.stop() //呼叫TaskSchedulerImpl.stop taskScheduler.stop() }
TaskSchedulerImpl.stop
TaskSchedulerImpl.stop會呼叫CoarseGrainedSchedulerBackend.stop
override def stop() { //停止推斷 speculationScheduler.shutdown() //呼叫CoarseGrainedSchedulerBackend.stop if (backend != null) { backend.stop() } //停止結果獲取 if (taskResultGetter != null) { taskResultGetter.stop() } starvationTimer.cancel() }
CoarseGrainedSchedulerBackend.stop
override def stop() { //呼叫stopExecutors() stopExecutors() try { if (driverEndpoint != null) { //傳送StopDriver訊號 driverEndpoint.askWithRetry[Boolean](StopDriver) } } catch { case e: Exception => throw new SparkException("Error stopping standalone scheduler's driver endpoint", e) } }
CoarseGrainedSchedulerBackend.stopExecutors
我們先來看下CoarseGrainedSchedulerBackend.stopExecutors
def stopExecutors() { try { if (driverEndpoint != null) { logInfo("Shutting down all executors") //傳送StopExecutors訊號 driverEndpoint.askWithRetry[Boolean](StopExecutors) } } catch { case e: Exception => throw new SparkException("Error asking standalone scheduler to shut down executors", e) } }
CoarseGrainedSchedulerBackend.DriverEndpoint.receiveAndReply
DriverEndpoint接收並回應該訊號:
case StopExecutors => logInfo("Asking each executor to shut down") for ((_, executorData) <- executorDataMap) { //給CoarseGrainedExecutorBackend傳送StopExecutor訊號 executorData.executorEndpoint.send(StopExecutor) } context.reply(true)
CoarseGrainedExecutorBackend.receive
CoarseGrainedExecutorBackend接收該訊號:
case StopExecutor => stopping.set(true) logInfo("Driver commanded a shutdown") //這裡並沒有直接關閉Executor, //因為Executor必須先返回確認幀給CoarseGrainedSchedulerBackend //所以,這的策略是給自己再發一個Shutdown訊號,然後處理 self.send(Shutdown) case Shutdown => stopping.set(true) new Thread("CoarseGrainedExecutorBackend-stop-executor") { override def run(): Unit = { // executor.stop() 會呼叫 `SparkEnv.stop()` // 直到 RpcEnv 徹底結束 // 但是, 如果 `executor.stop()` 執行在和RpcEnv相同的執行緒裡面, // RpcEnv 會等到`executor.stop()`結束後才能結束, // 這就產生了死鎖 // 因此,我們需要新建一個執行緒 executor.stop() }
Executor.stop
def stop(): Unit = { env.metricsSystem.report() //關閉心跳 heartbeater.shutdown() heartbeater.awaitTermination(10, TimeUnit.SECONDS) //關閉執行緒池 threadPool.shutdown() if (!isLocal) { //停止SparkEnv env.stop() } }
CoarseGrainedSchedulerBackend.DriverEndpoint.receiveAndReply
我們回過頭來看CoarseGrainedSchedulerBackend.stop,呼叫stopExecutors()結束後,會給 driverEndpoint傳送StopDriver訊號。CoarseGrainedSchedulerBackend.DriverEndpoint.接收訊號並回復:
case StopDriver => context.reply(true) //停止driverEndpoint stop()
作者:小爺Souljoy
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1834/viewspace-2819261/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spark Standalone模式 高可用部署Spark模式
- 深入理解Spark 2.1 Core (四):運算結果處理和容錯的原理Spark
- 部署spark2.2叢集(standalone模式)Spark模式
- Windows上搭建Standalone模式的Spark環境Windows模式Spark
- spark 2.1.0 standalone模式配置&&打包jar包透過spark-submit提交Spark模式JARMIT
- Reactor:深入理解reactor coreReact
- 《深入理解Spark》之Spark的整體執行流程Spark
- 深入理解代理模式模式
- 《深入理解Spark》之sparkSQL 處理流程SparkSQL
- 深入理解Aspnet Core之Identity(4)IDE
- 深入理解工廠模式模式
- 深入理解單例模式單例模式
- MVC設計模式深入理解MVC設計模式
- 深入淺出理解 Spark:環境部署與工作原理Spark
- standalone執行模式下 應用模式作業部署模式
- .NET Core 3.0之深入原始碼理解HttpClientFactory(二)原始碼HTTPclient
- .NET Core 3.0之深入原始碼理解HttpClientFactory(一)原始碼HTTPclient
- .NET Core 3.0之深入原始碼理解Configuration(三)原始碼
- .NET Core 3.0之深入原始碼理解Configuration(二)原始碼
- 深入原始碼理解Spark RDD的資料分割槽原理原始碼Spark
- 【Flink】深入理解Flink-On-Yarn模式Yarn模式
- Java核心(五)深入理解BIO、NIO、AIOJavaAI
- 使用.NET Core 2.1的Azure WebJobsWeb
- 深入理解[Future模式]原理與技術模式
- 深入理解 JavaScript 單例模式 (Singleton Pattern)JavaScript單例模式
- 深入理解Java記憶體模型(五)——鎖Java記憶體模型
- 深入理解 EF Core:EF Core 寫入資料時發生了什麼?
- 深入理解 EF Core:EF Core 讀取資料時發生了什麼?
- asp.net core 2.1 配置管理ASP.NET
- 深入理解Java的三種工廠模式Java模式
- 深入理解 JavaScript 單例模式及其應用JavaScript單例模式
- 深入理解javascript系列(五):變數物件(VO)2JavaScript變數物件
- Zookeeper原始碼分析(三) ----- 單機模式(standalone)執行原始碼模式
- 深入理解C#中的非同步(一)——APM模式EAP模式C#非同步模式
- 深入淺出Spark JoinSpark
- 深入理解[觀察者模式]原理與技術模式
- Entity Framework Core 2.1,新增種子資料Framework
- asp.net core 2.1 部署 centos7ASP.NETCentOS