標籤:spark executor內幕徹底解密
內容:
1、Spark Executor工作原理圖;
2、ExecutorBackend註冊源碼解密;
3、Executor執行個體化內幕;
4、Executor具體是如何工作的?
1、Master髮指令給Worker啟動Executor;
2、Worker接受到Master發送來的指令,通過ExecutorRunner啟動另外一個進程來運行Executor;
3、此時會啟動粗粒度的ExecutorBackend(CoarseGrainedExecutorBackend);
4、CoarseGrainedExecutorBackend通過發送RegisterExecutor向Driver註冊;
5、Driver在Executor註冊成功之後會返回RegisterExecutor資訊給CoarseGrainedExecutorBackend;
==========Spark Executor工作原理圖 ============
1、需要特別注意的是在CoarseGrainedExecutorBackend啟動時,向Driver註冊Executor其實質是註冊ExecutorBackend執行個體,和Executor執行個體之間沒有直接的關係;
override def onStart() {
logInfo("Connecting to driver: " + driverUrl)
rpcEnv.asyncSetupEndpointRefByURI(driverUrl).flatMap { ref =>
// This is a very fast action so we can use "ThreadUtils.sameThread"
driver = Some(ref)
ref.ask[RegisterExecutorResponse](
RegisterExecutor(executorId, self, hostPort, cores, extractLogUrls))
}(ThreadUtils.sameThread).onComplete {
// This is a very fast action so we can use "ThreadUtils.sameThread"
case Success(msg) => Utils.tryLogNonFatalError {
Option(self).foreach(_.send(msg)) // msg must be RegisterExecutorResponse
}
case Failure(e) => {
logError(s"Cannot register with driver: $driverUrl", e)
System.exit(1)
}
}(ThreadUtils.sameThread)
}
2、CoarseGrainedExecutorBackend是Executor運行所在的進程名稱,Executor才是真正處理Task對象所在,Executor內部是通過線程池的方式來完成Task的計算的;
3、CoarseGrainedExecutorBackend和Executor是一一對應的;
4、CoarseGrainedExecutorBackend是一個訊息通訊體(其實現了ThreadSafeRPCEndpoint),可以發訊息給Driver並可以接受Driver中發過來的指令,例如啟動Task等;
5、在Driver進程中,有兩個至關重要的EndPoint:
1)ClientEndpoint:主要負責向Master註冊當前的程式,是AppClient的內部成員;
2)DriverEndpoint:這是整個程式運行時候的磁碟機,是CoarseGrainedExecutorBackend的內部成員,這裡會接收到RegisterExecutor 資訊並完成在Driver中的註冊;
6、在Driver中通過ExecutorData封裝並註冊ExecutorBackend資訊到Driver記憶體資料結構executorMapData(CoarseGrainedSchedulerBackend的成員)
7、實際在執行的時候DriverEndpoint會把資訊吸入CoarseGrainedSchedulerBackend的記憶體資料結構executorMapData,所以說最終是確定註冊給了CoarseGrainedSchedulerBackend,也就是說CoarseGrainedSchedulerBackend掌握了為當前程式分配的所有的ExecutorBackend進程,而在每一個ExecutorBackend進行執行個體中會通過Executor對象來負責具體Task的運行。在啟動並執行時候使用synchronized關鍵字來保障executorMapData安全的並發寫操作;
8、CoarseGrainedExecutorBackend收到DriverEndpoint發送過來的RegisteredExecutor訊息後會啟動executor執行個體對象,而executor執行個體對象對象事實上負責真正的Task計算的;
9、建立的ThreadPool中以多線程並發執行和線程複用的方式來高效的執行Spark發過來的Task,接收到Task執行的命令後,會首先把Task封裝在TaskRunner裡面
override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
case RegisterExecutor(executorId, executorRef, hostPort, cores, logUrls) =>
if (executorDataMap.contains(executorId)) {
context.reply(RegisterExecutorFailed("Duplicate executor ID: " + executorId))
} else {
// If the executor‘s rpc env is not listening for incoming connections, `hostPort`
// will be null, and the client connection should be used to contact the executor.
val executorAddress = if (executorRef.address != null) {
executorRef.address
} else {
context.senderAddress
}
logInfo(s"Registered executor $executorRef ($executorAddress) with ID $executorId")
addressToExecutorId(executorAddress) = executorId
totalCoreCount.addAndGet(cores)
totalRegisteredExecutors.addAndGet(1)
val data = new ExecutorData(executorRef, executorRef.address, executorAddress.host,
cores, cores, logUrls)
// This must be synchronized because variables mutated
// in this block are read when requesting executors
CoarseGrainedSchedulerBackend.this.synchronized {
executorDataMap.put(executorId, data)
if (numPendingExecutors > 0) {
numPendingExecutors -= 1
logDebug(s"Decremented number of pending executors ($numPendingExecutors left)")
}
}
// Note: some tests expect the reply to come after we put the executor in the map
context.reply(RegisteredExecutor(executorAddress.host))
listenerBus.post(
SparkListenerExecutorAdded(System.currentTimeMillis(), executorId, data))
makeOffers()
}
private[cluster] class ExecutorData(
val executorEndpoint: RpcEndpointRef,
val executorAddress: RpcAddress,
override val executorHost: String,
var freeCores: Int,
override val totalCores: Int,
override val logUrlMap: Map[String, String]
) extends ExecutorInfo(executorHost, totalCores, logUrlMap)
// Start worker thread pool
private val threadPool = ThreadUtils.newDaemonCachedThreadPool("Executor task launch worker")
private val executorSource = new ExecutorSource(threadPool, executorId)
==========Executor是如何工作的============
1、當Driver發送過來Task的時候,其實是發送給了CoaresGrainedExecutorBackend這個RPCEndpoint,而不是直接發送給了Executor(Executor由於不是訊息迴圈體,所以永遠無法接收遠程發送過來的資訊)
2、ExecutorBackend在收到Driver中發送過來的訊息後會提供調用給launchTask來交給Executor去執行,然後交給線程池中的線程處理;
3、TaskRunner其實JAVA中的Runnable介面的具體實現,在真正工作的時候會交給線程池,線程池中的線程去運行,此時會調用run方法來執行Task;
4、TaskRunner在調用run方法的時候會調用Task的run方法,而Task的run方法會調用runTask,而實際Task有ShuffleMapTask和ResultTask;
case LaunchTask(data) =>
if (executor == null) {
logError("Received LaunchTask command but executor was null")
System.exit(1)
} else {
val taskDesc = ser.deserialize[TaskDescription](data.value)
logInfo("Got assigned task " + taskDesc.taskId)
executor.launchTask(this, taskId = taskDesc.taskId, attemptNumber = taskDesc.attemptNumber,
taskDesc.name, taskDesc.serializedTask)
}
def launchTask(
context: ExecutorBackend,
taskId: Long,
attemptNumber: Int,
taskName: String,
serializedTask: ByteBuffer): Unit = {
val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
serializedTask)
runningTasks.put(taskId, tr)
threadPool.execute(tr)
}
650) this.width=650;" src="/e/u261/themes/default/images/spacer.gif" style="background:url("/e/u261/lang/zh-cn/images/localimage.png") no-repeat center;border:1px solid #ddd;" alt="spacer.gif" />
王家林老師名片:
中國Spark第一人
新浪微博:http://weibo.com/ilovepains
公眾號:DT_Spark
部落格:http://blog.sina.com.cn/ilovepains
手機:18610086859
QQ:1740415547
郵箱:[email protected]
本文出自 “一枝花傲寒” 部落格,謝絕轉載!
Spark Executor內幕徹底解密(DT大資料夢工廠)