文章目录
前言
excutor反向注册,worker中为application启动的 executor,实际上是启动了这个 CoargeGrainedExecutorBackend进程,
/**
* 创建DriverEndpoint和DriverEndpointRef
* */
override def start() {
val properties = new ArrayBuffer[(String, String)]
for ((key, value) <- scheduler.sc.conf.getAll) {
if (key.startsWith("spark.")) {
properties += ((key, value))
}
}
// TODO (prashant) send conf instead of properties
// 创建driver的引用
driverEndpoint = createDriverEndpointRef(properties)
}
// 创建driver的引用
protected def createDriverEndpointRef(
properties: ArrayBuffer[(String, String)]): RpcEndpointRef = {
rpcEnv.setupEndpoint(ENDPOINT_NAME, createDriverEndpoint(properties))
}
// Launch tasks returned by a set of resource offers
/**
* launchTasks的处理步骤如下:
* 1.序列化TaskDescription
* 2.去除TaskDescription所描述任务分配的ExecutorData信息,并且将ExecutorData描述的空闲CPU核数减去任务所占用的核数。
* 3.向Executor所在的CoarseGrainedExecutorBackend进程中的CoarseGrainedExecutorBackend发送LaunchTask消息。
*
* 在该方法中,会将当前task在driver上序列化后发送到executor上运行。序列化后的task大小,如果超过128MB-200KB,当前
* task不能执行,并且把task对应的taskSetManager设置成zombie模式,因此其中剩余的task都不再执行。如果不超过该限制,
* 则会把task分发到executor上。
*
* 序列化后的Task发送到Executor上执行,是通过Akka来进行的。其实上面那个限制大小也是Akka决定的。在AkkaUtils类中可以
* 看到,这个限制由两个参数来确定spark.akka.frameSize,默认值为128,经过处理后,最终计算成128MB,表示Akka最大能传
* 递的消息大小。除了用于发送序列化后的task数据之外,Akka本身会使用到一部分空间存储一些额外的数据,这一部分的大小为200KB
* 。所以在默认情况下,对Akka来说,一个消息最大能传递的数据大小为128MB - 200KB。这两个参数在后面Executor中也会用到。
*/
private def launchTasks(tasks: Seq[Seq[TaskDescription]]) {
for (task <- tasks.flatten) {
// 序列化当前task
val serializedTask = TaskDescription.encode(task)
// 如果当前task序列化后的大小超过了128MB-200KB,跳过当前task,并把对应的taskSetManager置为zombie模式
if (serializedTask.limit >= maxRpcMessageSize) {
scheduler.taskIdToTaskSetManager.get(task.taskId).foreach { taskSetMgr =>
try {
var msg = "Serialized task %s:%d was %d bytes, which exceeds max allowed: " +
"spark.rpc.message.maxSize (%d bytes). Consider increasing " +
"spark.rpc.message.maxSize or using broadcast variables for large values."
msg = msg.format(task.taskId, task.index, serializedTask.limit, maxRpcMessageSize)
taskSetMgr.abort(msg)
} catch {
case e: Exception => logError("Exception in error callback", e)
}
}
}
// 序列化后的task大小不超过限制时,将当前task发送到Executor上执行。
else {
// 获取当前task分配到的executor相关信息
val executorData = executorDataMap(task.executorId)
executorData.freeCores -= scheduler.CPUS_PER_TASK
logDebug(s"Launching task ${task.taskId} on executor id: ${task.executorId} hostname: " +
s"${executorData.executorHost}.")
/**
* 发送运行task的消息,由org.apache.spark.executor.CoarseGrainedExecutorBackend.receive()方法处理
*/
executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
}
}
}
/**
* 调用Executor的launchTask方法时,标志着任务执行阶段的开始。执行过程如下:
*
* 1.创建TaskRunner,并且将其与taskId,taskName及serializedTask(这三个都被TaskDescription对象包含了)
* 添加到runningTasks = new ConcurrentHashMap[Long,TaskRuuner]中。
* 2.TaskRunner实现了Runnable接口(Scala中成为继承Runnable特质),最后使用线程池执行TaskRunner.
*
*/
def launchTask(context: ExecutorBackend, taskDescription: TaskDescription): Unit = {
//实例化一个TaskRunner对象来执行Task
val tr = new TaskRunner(context, taskDescription)
//将Task加入到正在运行的Task队列
runningTasks.put(taskDescription.taskId, tr)
/**
* 任务最终运行的地方
* 在worker的线程池中,
* 线程池调用org.apache.spark.executor.Executor.TaskRunner.run()方法运行任务
* 就是这个文件中的内部类
*/
threadPool.execute(tr)
}