Executor注册
注册的机制流程如下,CoarseGrainedExecutorBackend进程启动之后,会立即向Driver发送消息注册RegisterExecutor消息,Driver注册成功之后,会返回RegisteredExecutor消息。然后创建管理启动Task的句柄,将Task封装在TaskRunner线程中,并将其放入线程池运行。
直接看源码,如下所示:
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)
// 向Driver注册executor
ref.ask[RegisterExecutorResponse](
RegisterExecutor(executorId, self, hostPort, cores, extractLogUrls))
}(ThreadUtils.sameThread).onComplete {
// This is a very fast action so we can use "ThreadUtils.sameThread"
// 省略代码 ....
}
}(ThreadUtils.sameThread)
}
executor启动之后,首先会向Driver注册,发送RegisterExecutor消息,接着Driver接收到消息之后,将Executor的注册信息保存,然后发送RegisteredExecutor消息给Executor,如下所示:
override def receive: PartialFunction[Any, Unit] = {
// Driver注册Executor成功之后,会发送回来RegisteredExecutor消息
case RegisteredExecutor(hostname) =>
logInfo("Successfully registered with driver")
// 创建Executor对象,作为执行句柄,它的大部分功能都是通过executor实现的
executor = new Executor(executorId, hostname, env, userClassPath, isLocal = false)
case RegisterExecutorFailed(message) =>
logError("Slave registration failed: " + message)
System.exit(1)
// 启动Task
case LaunchTask(data) =>
if (executor == null) {
logError("Received LaunchTask command but executor was null")
System.exit(1)
} else {
// 将Task反序列化
val taskDesc = ser.deserialize[TaskDescription](data.value)
logInfo("Got assigned task " + taskDesc.taskId)
// 用executor执行句柄调用launchTask,来执行task
executor.launchTask(this, taskId = taskDesc.taskId, attemptNumber = taskDesc.attemptNumber,
taskDesc.name, taskDesc.serializedTask)
}
// 省略代码.................
}
executor在接收到Driver发送的RegisteredExecutor消息之后,就会创建一个executor句柄。然后Executor在接收到来自Driver内部的TaskScheduler发送的LaunchTask消息,会负责启动Task,首先将task反序列化,接着调用launchTask启动task。下面看一下launchTask方法()。
def launchTask(
context: ExecutorBackend,
taskId: Long,
attemptNumber: Int,
taskName: String,
serializedTask: ByteBuffer): Unit = {
// 将对于每一个task,都会创建一个TaskRunner,TaskRunner继承的是Java的多线程中的Runnable接口
val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
serializedTask)
// 将TaskRunner放入内存缓存
runningTasks.put(taskId, tr)
// 从线程池中取出一个线程,执行task。
// 线程池自动实现了排队机制,也就是说,如果线程池内的线程暂时没有空闲的话,
// 那么后续进来的线程需要排队
threadPool.execute(tr)
}
在launchTask中最重要的就是创建TaskRuuner,它是一个线程,里面封装了task的一些信息,将其加入runningTasks缓存,最后放入线程池中去运行。