线程池
消息派发器中的线程池“dispatcher-event-loop”
这个线程池,是用来派发Spark中Endpoint的消息的,比如OnStart,RpcMessage等。
这个线程池创建的时机是在Dispatcher对象创建的初始化过程完成的。
这个线程池的线程数选择是计算机的CPU核数,而且采用的是Fixed线程池。
/** Thread pool used for dispatching messages. */
private val threadpool: ThreadPoolExecutor = {
val numThreads = nettyEnv.conf.getInt("spark.rpc.netty.dispatcher.numThreads",
math.max(2, Runtime.getRuntime.availableProcessors()))
val pool = ThreadUtils.newDaemonFixedThreadPool(numThreads, "dispatcher-event-loop")
for (i <- 0 until numThreads) {
pool.execute(new MessageLoop)
}
pool
}
派发消息的线程的执行策略,当每一个线程启动且都在运行它们的run方法时:
- 一开始,所有的派发线程都通过
val data = receivers.take()
操作阻塞在共享阻塞队列上。 - 当有端点通过注册操作将端点通过
receivers.offer()
操作到Dispatcher的端点阻塞队列时,所有的派发线程被唤醒。由于take操作是加锁操作,有且只有一个线程能获取到该锁takeLock,意味着,唤醒的线程中有且只有一个线程能够获取到注册的端点。按照时间顺序,第一个端点是在创建完Netty服务端之后注册的endpoint-verifier端点。 - 获取到端点的线程继续执行
data.inbox.process(Dispatcher.this)
。表示,每一个端点处理自己的邮箱inbox内存放的消息InboxMessage。 - 从该邮箱inbox中获取到消息InboxMessage,消息存放到该集合的时机在创建端点EndpointData的时候(如果分不清EndpointData,RpcEndpoint,NettyRpcEndpointRef之间的关系,看Spark源码精读一心跳机制)。
- 端点的消息匹配逻辑。这里讲述的是注册endpoint-verifier端点。此时处理的消息是OnStart。即启动endpoint-verifier。其他消息,比如RpcMessage,可以看Spark源码精读一心跳机制。
混洗数据客户端线程池“shuffle-client”
这个线程池其实是Netty中的线程池。不再赘述其实现逻辑,详细看Netty网络编程模型
Netty的超时调度器“netty-rpc-env-timeout”
val timeoutScheduler = ThreadUtils.newDaemonSingleThreadScheduledExecutor("netty-rpc-env-timeout")
检测一个Rpc请求是否超时。
该检测实现逻辑在NettyRpcEnv的ask函数中:
- 创建一个对象名为promise的不可变Promise对象
val promise = Promise[Any]()
。这个Promise是一个object,可视作Java类的单例对象。由于在该Promise中,定义了apply方法def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]()
,因此,可简写成Promise[Any]()
,否则"Promise.type does not take parameters",这是语法。 在实例化DefaultPromise对象时,通过updateState(null, Nil) // The promise is incomplete and has no callbacks
设置了初始值。 - 定义一个本地环境的许可p
- 通过dispatcher投递消息跟许可p
- 当执行完
dispatcher.postLocalMessage(message, p)
后,promise的值被设置成Success(true)。通过context.reply(true)
回调onSuccess函数完成该promise的设值操作。 - 通过该超时调度器定时执行onFailure函数
val timeoutCancelable = timeoutScheduler.schedule(new Runnable { override def run(): Unit = { onFailure(new TimeoutException(s"Cannot receive any reply in ${timeout.duration}")) } }, timeout.duration.toNanos, TimeUnit.NANOSECONDS)
- 如果promise的值为Success(True),则取消定时任务onFailure的执行。
// 发送消息的操作完成,取消定时任务onFailure promise.future.onComplete { v => timeoutCancelable.cancel(true) }(ThreadUtils.sameThread)
Netty的RPC连接线程池“netty-rpc-connection”
// Because TransportClientFactory.createClient is blocking, we need to run it in this thread pool
// to implement non-blocking send/ask.
// TODO: a non-blocking TransportClientFactory.createClient in future
private[netty] val clientConnectionExecutor = ThreadUtils.newDaemonCachedThreadPool(
"netty-rpc-connection",
conf.getInt("spark.rpc.connect.threads", 64))
混洗数据服务端的线程池“shuffle-server”
Netty服务端流程详见Netty的编程模型
// 定义函数变量 函数变量名: 函数类型 = { 函数表达式 }
// 这个函数类型组成 入参类型 => 函数结果二元组
val startNettyRpcEnv: Int => (NettyRpcEnv, Int) = { actualPort =>
// 由startService=startNettyRpcEnv触发
// val (service, port) = startService(tryPort)
nettyEnv.startServer(config.bindAddress, actualPort)
(nettyEnv, nettyEnv.address.port)
}
try {
Utils.startServiceOnPort(config.port, startNettyRpcEnv, sparkConf, config.name)._1
} catch {
case NonFatal(e) =>
nettyEnv.shutdown()
throw e
}