上一节介绍了RpcEndpoint、RpcEndpointRef和RpcEnv,这一节主要是介绍Dispatcher消息分发器,当RpcEnv不管是收到外部的消息分发还是内部RpcEndpoint之间消息发送都需要经由Dispatcher处理,Dispatcher类似于一个本地邮局,将外地信件发到具体的人家,只不过不负责将信件发到远处,只负责同一个地区的信件发送。
Dispatcher源码
//private[netty]表示只能在netty包内部被继承使用,netty包以外不能继承和使用
private[netty] class Dispatcher(nettyEnv: NettyRpcEnv, numUsableCores: Int) extends Logging {
//Dispatcher内负责管理多个endpoint消息收发,CurrentMap的key是节点的名字,value是endpoint对应的消息循环,
// 该循环可以是共享的也可以是节点独有的
private val endpoints: ConcurrentMap[String, MessageLoop] =
new ConcurrentHashMap[String, MessageLoop]
//节点和该节点对应的引用的映射,通过节点可以获取对应的引用,通过引用来给该节点发消息
private val endpointRefs: ConcurrentMap[RpcEndpoint, RpcEndpointRef] =
new ConcurrentHashMap[RpcEndpoint, RpcEndpointRef]
//计数器 子线程结束以后即shutdownLatch=0 表示主线程可以开始执行
private val shutdownLatch = new CountDownLatch(1)
//共享消息循环 即多个节点共同使用的循环
private lazy val sharedLoop = new SharedMessageLoop(nettyEnv.conf, this, numUsableCores)
//表示Dispatcher的状态--停止,GuardedBy表示stopped变量在使用之前需要获取该变量的锁
@GuardedBy("this")
private var stopped = false
//RpcEndpoint注册 1.主要是建立节点和引用之间的映射关系 2.将该节点注册到共享消息循环中
def registerRpcEndpoint(name: String, endpoint: RpcEndpoint): NettyRpcEndpointRef = {
//节点地址
val addr = RpcEndpointAddress(nettyEnv.address, name)
//节点引用
val endpointRef = new NettyRpcEndpointRef(nettyEnv.conf, addr, nettyEnv)
synchronized {
if (stopped) {//stopped为true说明该Dispatcher所在环境RpcEnv已经停止了
throw new IllegalStateException("RpcEnv has been stopped")
}
if (endpoints.containsKey(name)) {//如果已经包含该节点 提示已经注册
throw new IllegalArgumentException(s"There is already an RpcEndpoint called $name")
}
//添加节点和引用的映射
endpointRefs.put(endpoint, endpointRef)
//创建消息循环
var messageLoop: MessageLoop = null
try {
messageLoop = endpoint match {
case e: IsolatedRpcEndpoint =>//单个节点的消息循环
new DedicatedMessageLoop(name, e, this)
case _ =>//将该节点注册到共享消息循环中,后续收到该节点的消息发送到指定的节点对应的inbox
sharedLoop.register(name, endpoint)
sharedLoop
}
endpoints.put(name, messageLoop)
} catch {
case NonFatal(e) =>
endpointRefs.remove(endpoint)
throw e
}
}
endpointRef
}
//获取节点的引用
def getRpcEndpointRef(endpoint: RpcEndpoint): RpcEndpointRef = endpointRefs.get(endpoint)
//移除该节点的引用
def removeRpcEndpointRef(endpoint: RpcEndpoint): Unit = endpointRefs.remove(endpoint)
//注销节点
private def unregisterRpcEndpoint(name: String): Unit = {
//消息循环中移除该节点
val loop = endpoints.remove(name)
if (loop != null) {
loop.unregister(name)
}
}
//停止指定的节点
def stop(rpcEndpointRef: RpcEndpointRef): Unit = {
synchronized {
if (stopped) {
// This endpoint will be stopped by Dispatcher.stop() method.
return
}//注销该节点
unregisterRpcEndpoint(rpcEndpointRef.name)
}
}
//给该Dispatcher下的所有节点发消息
def postToAll(message: InboxMessage): Unit = {
//遍历endpoints找到所有的节点 依次发送消息
val iter = endpoints.keySet().iterator()
while (iter.hasNext) {
val name = iter.next
//给指定的节点发送消息
postMessage(name, message, (e) => { e match {
case e: RpcEnvStoppedException => logDebug(s"Message $message dropped. ${e.getMessage}")
case e: Throwable => logWarning(s"Message $message dropped. ${e.getMessage}")
}}
)}
}
//将远程节点的消息发到执行的节点 远程消息中包含目标节点名称
def postRemoteMessage(message: RequestMessage, callback: RpcResponseCallback): Unit = {
//创建远程节点上下文环境
val rpcCallContext =
new RemoteNettyRpcCallContext(nettyEnv, callback, message.senderAddress)
//获取远程节点消息
val rpcMessage = RpcMessage(message.senderAddress, message.content, rpcCallContext)
//从消息中获取目标节点名称 并发送该消息
postMessage(message.receiver.name, rpcMessage, (e) => callback.onFailure(e))
}
//发送本地几点的消息给本地另外一个节点 这里指的是同一个进程内的节点之间消息发送 p表示'Promise'表示回复
def postLocalMessage(message: RequestMessage, p: Promise[Any]): Unit = {
val rpcCallContext =
new LocalNettyRpcCallContext(message.senderAddress, p)
val rpcMessage = RpcMessage(message.senderAddress, message.content, rpcCallContext)
postMessage(message.receiver.name, rpcMessage, (e) => p.tryFailure(e))
}
//发送单向消息
def postOneWayMessage(message: RequestMessage): Unit = {
postMessage(message.receiver.name, OneWayMessage(message.senderAddress, message.content),
(e) => e match {
case re: RpcEnvStoppedException => logDebug(s"Message $message dropped. ${re.getMessage}")
case _ => throw e
})
}
//给指定的节点发送消息
private def postMessage(
endpointName: String,
message: InboxMessage,
callbackIfStopped: (Exception) => Unit): Unit = {
val error = synchronized {
//根据节点名获取消息循环
val loop = endpoints.get(endpointName)
//Dispatcher停止状态 则抛出异常
if (stopped) {
Some(new RpcEnvStoppedException())
} else if (loop == null) {//消息循环为空 抛出异常
Some(new SparkException(s"Could not find $endpointName."))
} else {
//给指定的节点的inbox发送消息
loop.post(endpointName, message)
None
}
}
error.foreach(callbackIfStopped)
}
//将Dispatcher状态改为停止
def stop(): Unit = {
synchronized {
if (stopped) {
return
}
stopped = true
}
var stopSharedLoop = false
//遍历endpoints所有节点 并注销每个节点
endpoints.asScala.foreach { case (name, loop) =>
unregisterRpcEndpoint(name)
if (!loop.isInstanceOf[SharedMessageLoop]) {//如果是单个消息循环则直接将消息循环停止
loop.stop()
} else {
stopSharedLoop = true
}
}
if (stopSharedLoop) {//如果是消息循环则停止该共享消息循环
sharedLoop.stop()
}
shutdownLatch.countDown()//计数器减一
}
//使Dispatcher进程等待,通常由rpcEnv来调用
def awaitTermination(): Unit = {
shutdownLatch.await()
}
/**
* Return if the endpoint exists
*///确认endpoints是否包含该节点
def verify(name: String): Boolean = {
endpoints.containsKey(name)
}
}
Dispatcher内部有一个Map类型的变量endpoints用来保存RpcEndpoint端点和消息循环MessageLoop的映射关系,但是通常一个RpcEnv环境中会管理多个RpcEndpoint,所以多个RpcEndpoint会对应一个共享消息循环(下节会提到MeaasgeLoop)。同样是Map类型的变量endpointRefs用来存储RpcEndpoint和RpcEndpointRef的映射关系,方便找到端点的引用,sharedLoop是一个共享消息循环,Dispatcher本身是可以关闭的,一旦关闭就要注销所有注册的节点。
registerRpcEndpoint用来注册RpcEndpoint,1.主要是建立节点和引用之间的映射关系 2.将该节点注册到共享消息循环中,并不是停止该节点(节点的启动和停止由收到的消息来实现,后面会讲)有同学看了上一节可能会觉得奇怪不是说RpcEnv负责注册管理RpcEndpoint吗?怎么这里又说Dispatcher注册,这里不矛盾,因为一个RpcEnv有一个Dispatcher,具体注册登记管理是由Dispatcher来实现的。
unregisterRpcEndpoint方法负责将消息循环中的节点映射关系移除,并且移除节点和引用的对应关系。
postToAll用来给该sharedLoop消息循环中的所有节点发送消息。
postRemoteMessage方法主要是将远程节点的消息发到本Env环境中的节点,消息中包含目标节点名称,从而可以找到对应的节点的Inbox收件箱,进而将消息发到该节点。
postLocalMessage用来将消息发到本地Env环境中的其他节点。
postOneWayMessage发送一条单向消息。
postMessage给指定的节点发送消息,首先通过目标节点名称找到该节点的共享消息循环,然后调用消息循环中的Inbox的post方法给Inbox发消息(后续会讲到Inbox收件箱)。
注:节点表示RpcEndpoint端点
总结:一个RpcEnv有一个Dispatcher,Dispatcher负责RpcEndpoint端点的注册及管理,并负责将消息传递给具体的RpcEndpoint端点,同时维护RpcEndpoint端点和其引用的映射关系。有一些Scala或者Java的基础知识点在注视中补充了,方便理解,同时有一些还没讲到包括MessageLoop消息循环和Inbox收件箱,后面会讲到。阅读源码就没有画图了,如果不理解Dispatcher在通信中的位置可以看一下概述,里面有讲到。