Spark源码阅读篇-Rpc通信-Dispatcher消息分发器

110 阅读4分钟

上一节介绍了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在通信中的位置可以看一下概述,里面有讲到。