spark下的Task分发
文章目录
- spark下的Task分发
- 前言
- 一、Inbox类
- 1.1 process方法。
- 1.2 post方法。
- 二、Dispatcher类
- 2.1 构造器与成员变量
- 2.2 postMessage方法
- 2.3 MessageLoop方法
- 三. outBox类
- 3.1主要成员变量
- 3.2主要的内部类
- 3.2send方法
- 3.2drainOutbox方法
- 四. CoarseGrainedExecutorBackend
- 3.1 onStart()方法
- 3.2 receive方法
- 五.CoarseGrainedSchedulerBackend
- 5.1调度接口
- 5.2重要的成员变量
- 5.2.1executorDataMap
- 5.2.2 DriverEndpoint
- 5.2.2.1 onStart方法
- 5.2.2.2 recieve方法
- 5.2.2.3 分发任务给executor
- 5.2.2.4 launchTasks
- 六 NettyRpcEndpointRef
- 6.1send方法
- 七 NettyRpcEnv
- 7.1重要的成员变量
- 7.2 Send方法
- 7.2 postToOutbox方法
- 7.3 executor执行分发过来的task
- 结尾
前言
1.不用多说,继续学习任务调度的分发流程O(∩_∩)O。前期学习netty目标也是为了理解spark这一块通信的内容。
2.从边缘地带一步步向中心大陆进军~~。
一、Inbox类
1.inbox就是用来存储inboxmessage以及将该message进行发送出去的类。大神代码写的太优雅了/(ㄒoㄒ)/~~ 。在该构造器中着重关注的对象当然是LinkedList对象了,它装载的就是待发出去的信息,一般都是发到本地的。
2.每一个Inbox都存储了用来处理该message对应的endpoint以及endpointRef引用对象。
1.1 process方法。
1.专门用来将存储在LinkedList对象下的message发送出去的一个方法。其实它处理这个message信息就是委托了它所持有的endpoint对象帮处理。
2.在这里传入这个dispatcher对象用到的时候仅是发送stop 信息后它需要将当前endpoint进行从其维护的列表里移除然后停止这么一个i行为。
/**
* Process stored messages.
*/
def process(dispatcher: Dispatcher): Unit = {
var message: InboxMessage = null
inbox.synchronized {
if (!enableConcurrent && numActiveThreads != 0) {
return
}
message = messages.poll()
if (message != null) {
numActiveThreads += 1
} else {
return
}
}
while (true) {
safelyCall(endpoint) {
message match {
case RpcMessage(_sender, content, context) =>
try {
endpoint.receiveAndReply(context).applyOrElse[Any, Unit](content, { msg =>
throw new SparkException(s"Unsupported message $message from ${_sender}")
})
} catch {
case NonFatal(e) =>
context.sendFailure(e)
// Throw the exception -- this exception will be caught by the safelyCall function.
// The endpoint's onError function will be called.
throw e
}
case OneWayMessage(_sender, content) =>
endpoint.receive.applyOrElse[Any, Unit](content, { msg =>
throw new SparkException(s"Unsupported message $message from ${_sender}")
})
case OnStart =>
endpoint.onStart()
if (!endpoint.isInstanceOf[ThreadSafeRpcEndpoint]) {
inbox.synchronized {
if (!stopped) {
enableConcurrent = true
}
}
}
case OnStop =>
val activeThreads = inbox.synchronized { inbox.numActiveThreads }
assert(activeThreads == 1,
s"There should be only a single active thread but found $activeThreads threads.")
dispatcher.removeRpcEndpointRef(endpoint)
endpoint.onStop()
assert(isEmpty, "OnStop should be the last message")
case RemoteProcessConnected(remoteAddress) =>
endpoint.onConnected(remoteAddress)
case RemoteProcessDisconnected(remoteAddress) =>
endpoint.onDisconnected(remoteAddress)
case RemoteProcessConnectionError(cause, remoteAddress) =>
endpoint.onNetworkError(cause, remoteAddress)
}
}
inbox.synchronized {
// "enableConcurrent" will be set to false after `onStop` is called, so we should check it
// every time.
if (!enableConcurrent && numActiveThreads != 1) {
// If we are not the only one worker, exit
numActiveThreads -= 1
return
}
message = messages.poll()
if (message == null) {
numActiveThreads -= 1
return
}
}
}
}
1.2 post方法。
这个方法就是用来存储待发送的信息,是线程安全的。
二、Dispatcher类
这个类看名字就知道用来分发message的。
2.1 构造器与成员变量
1.其内部有一个内部类EndpointData,专门用来维护endpoint的名字与对应的inbox对象,如其名字就看成要发送的数据。
2.endpoints用来存储EndpointData对象,当向哪个endpoint发送数据的时候同过该endpoint名字查找即可找到。
3.receivers,一个存储即将需要发送出去的数据的队列。
4.threadpool ,一个线程池默认是操作系统CPU核数的2倍,其专门用来分发信息。
2.2 postMessage方法
直接通过endpoint名字从endpoints获取其要存储信息的Inbox,因为这个inbox跟EndpointData对象一一对应。
2.3 MessageLoop方法
正如代码看到的就是当前线程不停地从blockingQueue里取数据出来,然后丢给对应的inbox进行处理。
三. outBox类
1.咱们看一下其构造器传参数,主要保存nettyEnv对象以及一个remote rpc地址这与inbox保存endpoint以及其引用不一样。nettyEnv用来创建连接的客户端或者是删除不需要维护的outbox。
2.看里面的成员变量,它持有TransportClient,一个netty的客户端专门用来与远程的endpoint节点进行通信的,这是inbox所没有的。
。
3.1主要成员变量
3.2主要的内部类
有两个内部类,都是用来发信息,主要关注sendWith方法,一个有回调函数,一个没有。
3.2send方法
当调用的时候先将message添加进list中,然后开始将数据发送出去
3.2drainOutbox方法
将list里面的信息都往远程节点发送直至排空,核心的两行代码就是从list里面获取message,然后委托message对象下的nettyClient将数据发送到nettyServer端帮处理该信息。
四. CoarseGrainedExecutorBackend
1.再回到该类,上一篇在写cluster模式提交的时候咱们就看到了这个类的对象在最后被在一个container里启动并等待分发任务。但这次它是以一个endpoint对象的角色进行观察它在干啥。
2.既然是以一个endpoint的对象观察它,就看一下endpoint有哪些方法。它又是干啥的。从该接口注释看:一个为RPC专门定义的类,该类里的成员方法会因为不同类型的message会触发到,且endpoint的声明周期是[构造器->onStart->receive(多次)->onStop]上面看Inbox的时候也看到了,在实例inbox的时候就将一个OnStart 信息add进入了linkedList队列中。后面就调用了该endpoint的onstart方法了。
3.endpoint处了上面声明周期的方法外还有onDisconnected,onConnected等接口方法。
3.1 onStart()方法
endpoint接收到onStart信号后就开始后去driver的url,然后进行反向注册。
3.2 receive方法
一旦onstart且注册成功后就开始接收自己的或来自driver端的message类型,然后进行判断走对应的代码分支执行。
1.当下看到的 RegisteredExecutor和后面接收派发的任务的信息LaunchTask(data)类型。
五.CoarseGrainedSchedulerBackend
就是这么一个粗粒度调度器专门分发任务给各个executor干活的,看一下它实现的接口ExecutorAllocationClient的注释,一个用于与集群通信的客户端目前仅支持yarn模式。
5.1调度接口
与任务相关的操作方法。
5.2重要的成员变量
5.2.1executorDataMap
(1)下面用来维护各个executor的列表方便分发task给executor干活
5.2.2 DriverEndpoint
driver端用于与其他executor通信的内部成员类,是它负责将task分发出去。接下来看一下重要的方法
5.2.2.1 onStart方法
在启动后每隔一秒钟就调用endpointRef的send方法将一条message发送出去。
5.2.2.2 recieve方法
根据接收到message类型匹配到后执行对应的分支代码。
5.2.2.3 分发任务给executor
5.2.2.4 launchTasks
(1)启动任务,这里也提到到了如果序列化的任务数据字节比特太大,大于默认的128x1024x1024字节就会建议开启广播变量进行序列化传输任务数据。或者说将默认的128这个值调大但不能超过Int.MaxValue/1024/1024否则就抛异常。
(2)一条重要的代码 executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
就是将序列化的任务包装成LaunchTask
六 NettyRpcEndpointRef
上面已经看到了它要将LaunchTask类型的信息通过EndpointRef的send方法进行发送出去。我们就看一下这个send的方法
6.1send方法
它又将LaunchTask任务又进一步包装,进行适配enttyEnv的send这个方法,委托它将信息发送出去。
七 NettyRpcEnv
7.1重要的成员变量
分发器前面也见到了就一般就自己与自己通信,
transport*就是与远程executor通信关联的变量
7.2 Send方法
正如6.1看到的它就委托当前这个方法将message发出去。
7.2 postToOutbox方法
将message发送到executor,这里这个message对象就是上面OutboxMessage这个内部类,可以追回3.2的方法看它通过传入的客户端将信息发出去了。
7.3 executor执行分发过来的task
回到类CoarseGrainedExecutorBackend。看receive方法,然后看关键代码行executor.launchTask这个方法。
结尾
1.到此快速过完了一遍executor起来后等待driver调度分发任务,在这个分发任务的过程中,message信息被层层包装进行适配来自不同类的方法。
2.inbox主要处理本地的RPC,outbox主要处理remote RPC的信息。
3.当序列化的task字节大于128x1024x1024字节的时候,建议你要么使用广播变量要播继续调大这个值。