Spark源码阅读篇-Rpc通信-NettyRpcEnv

117 阅读13分钟

上一节介绍了消息发件箱Outbox,这一节介绍NettyRpcEnv,NettyRpcEnv囊括了之前介绍的所有,是RpcEnv的netty实现,不仅包括消息分发器Dispatcher、消息循环MessageLoop、收件箱Inbox和发件箱Outbox,而且还包括文件下载服务器、流管理器、客户端工厂,流读取通道和客户端下载通道等等,定义netty实现的引用NettyRpcEndpointRef,以及传输消息处理句柄NettyRpcHandler。

NettyRpcEnv源码

private[netty] class NettyRpcEnv(
    val conf: SparkConf,
    javaSerializerInstance: JavaSerializerInstance,
    host: String,
    securityManager: SecurityManager,//负责安全
    numUsableCores: Int) extends RpcEnv(conf) with Logging {
  //获取角色类型是driver还是executor
  val role = conf.get(EXECUTOR_ID).map { id =>
    if (id == SparkContext.DRIVER_IDENTIFIER) "driver" else "executor"
  }

  //传输配置
  private[netty] val transportConf = SparkTransportConf.fromSparkConf(
    conf.clone.set(RPC_IO_NUM_CONNECTIONS_PER_PEER, 1),
    "rpc",//使用rpc模式通信
    conf.get(RPC_IO_THREADS).getOrElse(numUsableCores),//线程数
    role)//角色
  
  //消息分发器
  private val dispatcher: Dispatcher = new Dispatcher(this, numUsableCores)
  
  //流管理器 主要是服务于NettyRpcEnv中的文件 方便文件的流传输
  private val streamManager = new NettyStreamManager(this)
  
  //传输上下文环境 为基础客户端和服务器启用TransportContext初始化。
  private val transportContext = new TransportContext(transportConf,
    new NettyRpcHandler(dispatcher, this, streamManager))
  
  //创建客户端引导带
  //这使得能够在每次连接的基础上进行初始信息交换(例如,SASL身份验证令牌)
  private def createClientBootstraps(): java.util.List[TransportClientBootstrap] = {
    if (securityManager.isAuthenticationEnabled()) {
      java.util.Arrays.asList(new AuthClientBootstrap(transportConf,
        securityManager.getSaslUser(), securityManager))
    } else {
      java.util.Collections.emptyList[TransportClientBootstrap]
    }
  }

  //客户端工厂 createClientFactory--在返回新客户端之前初始化运行给定TransportClientBootstrap的ClientFactory。
  //引导程序将同步执行,并且必须成功运行才能创建客户端。
  private val clientFactory = transportContext.createClientFactory(createClientBootstraps())

   //用于文件下载的独立客户端工厂。这避免了使用与主RPC上下文相同的RPC处理程序,从而使这些客户端引起的事件与主RPC通信保持隔离。
   //它还允许对某些属性进行不同的配置,例如每个对等点的连接数。
   //TransportClientFactory--工厂维护与其他主机的连接池,并应为同一远程主机返回相同的TransportClient。
   //它还为所有TransportClient共享一个工作线程池。
   //@volatile注解将字段标记为易失的(可以被多个线程同时跟新),对应volatile;
  @volatile private var fileDownloadFactory: TransportClientFactory = _

  //超时线程任务 默认情况下,取消的任务在其延迟过去之前不会自动从工作队列中删除。必须手动启用它。
  val timeoutScheduler = ThreadUtils.newDaemonSingleThreadScheduledExecutor("netty-rpc-env-timeout")

  //因为TransportClientFactory.createClient是阻塞的,我们需要在线程池里面运行它以达到非阻塞的发送和请求
  //一个非阻塞的TransportClientFactory.createClient未来会实现的
  //客户端连接执行器
  private[netty] val clientConnectionExecutor = ThreadUtils.newDaemonCachedThreadPool(
    "netty-rpc-connection",
    conf.get(RPC_CONNECT_THREADS))

  //TransportServer--高效、低级别流媒体服务的服务器。
  @volatile private var server: TransportServer = _

  //AtomicBoolean--它提供了原子性的操作,可以保证对布尔值的读写操作的线程安全性。
  //表示状态 默认表示开启状态
  private val stopped = new AtomicBoolean(false)

   //一个关于RpcAddress和Outbox的映射,当我们准备连接到一个远程地址时,只需要将消息放到OutBox里面
   //进而实现一种非阻塞的发送方式
   //发件箱
  private val outboxes = new ConcurrentHashMap[RpcAddress, Outbox]()

   //通过地址移除OutBox 然后停止该发件箱
  private[netty] def removeOutbox(address: RpcAddress): Unit = {
    val outbox = outboxes.remove(address)
    if (outbox != null) {
      outbox.stop()
    }
  }

  //启动传输服务器
  def startServer(bindAddress: String, port: Int): Unit = {
    //创建传输引导带
    val bootstraps: java.util.List[TransportServerBootstrap] =
      //如果授权通过
      if (securityManager.isAuthenticationEnabled()) {
        java.util.Arrays.asList(new AuthServerBootstrap(transportConf, securityManager))
      } else {
        java.util.Collections.emptyList()
      }
    //创建服务器
    server = transportContext.createServer(bindAddress, port, bootstraps)
    //RpcEndpointVerifier--远程[[RpcEnv]]的[[RpcEndpoint]]要查询是否存在“RpcEndpoint”。
    //这在设置远程端点引用时使用。
    //def registerRpcEndpoint(name: String, endpoint: RpcEndpoint): NettyRpcEndpointRef
    //this指的是NettyRpcEnv
    dispatcher.registerRpcEndpoint(
      RpcEndpointVerifier.NAME, new RpcEndpointVerifier(this, dispatcher))
  }

  //@Nullable表示这些元素可以为nul
  //lazy--只有在调用惰性变量时,才会去实例化这个变量
  //RPC环境的地址,包括主机名和端口。
  @Nullable
  override lazy val address: RpcAddress = {
    if (server != null) RpcAddress(host, server.getPort()) else null
  }

  //设置endpoint节点
  override def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef = {
    dispatcher.registerRpcEndpoint(name, endpoint)
  }

  //按照uri异步设置引用EndpointRef
  def asyncSetupEndpointRefByURI(uri: String): Future[RpcEndpointRef] = {
    //节点的地址标识
    val addr = RpcEndpointAddress(uri)
    //引用
    val endpointRef = new NettyRpcEndpointRef(conf, addr, this)
    //标识符
    val verifier = new NettyRpcEndpointRef(
      conf, RpcEndpointAddress(addr.rpcAddress, RpcEndpointVerifier.NAME), this)
    //判断是否已经有该标识符 有则说明已经有该引用了 否则抛出没找到该节点异常
    verifier.ask[Boolean](RpcEndpointVerifier.CheckExistence(endpointRef.name)).flatMap { find =>
      if (find) {
        Future.successful(endpointRef)
      } else {
        Future.failed(new RpcEndpointNotFoundException(uri))
      }//一个“ExecutionContextExecutor”,它在调用“execute/submit”的线程中运行每个任务。
      //调用方应确保在此“ExecutionContextExecutor”中运行的任务很短且从不阻塞。
    }(ThreadUtils.sameThread)
  }
  
  //停止某个引用
  override def stop(endpointRef: RpcEndpointRef): Unit = {
    //断言 如果不是NettyRpcEndpointRef 则抛出异常
    require(endpointRef.isInstanceOf[NettyRpcEndpointRef])
    //在分发器中停止该引用
    dispatcher.stop(endpointRef)
  }

  //发消息到发件箱 消息接收者是节点的引用 
  private def postToOutbox(receiver: NettyRpcEndpointRef, message: OutboxMessage): Unit = {
    if (receiver.client != null) {
      //引用客户端不为空则将消息发给发送客户端
      message.sendWith(receiver.client)
    } else {
      //如果目标发件箱为空 则新建一个
      //断言 如果目标地址为空则抛出异常
      require(receiver.address != null,
        "Cannot send message to client endpoint with no listen address.")
      val targetOutbox = {
        //获取发件箱
        val outbox = outboxes.get(receiver.address)
        if (outbox == null) {
          //创建发件箱
          val newOutbox = new Outbox(this, receiver.address)
          //不管该地址绑定的什么改为绑定新的发件箱
          //putIfAbsent--如果指定的键尚未与值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
          val oldOutbox = outboxes.putIfAbsent(receiver.address, newOutbox)
          if (oldOutbox == null) {
            newOutbox
          } else {
            oldOutbox
          }
        } else {
          outbox
        }
      }
      //如果环境停止了
      if (stopped.get) {
        //移除该发件箱
        outboxes.remove(receiver.address)
        //停止该发件箱
        targetOutbox.stop()
      } else {
        //发件箱向对应的节点发消息 添加消息到消息循环里面
        targetOutbox.send(message)
      }
    }
  }

  //发消息
  private[netty] def send(message: RequestMessage): Unit = {
    //目标地址
    val remoteAddr = message.receiver.address
    //如果目标地址是rpc环境 即本地发送消息
    if (remoteAddr == address) {
      // Message to a local RPC endpoint.
      try {
        //由消息分发器发送该条单向消息
        dispatcher.postOneWayMessage(message)
      } catch {
        //如果rpc环境异常则打印该异常
        case e: RpcEnvStoppedException => logDebug(e.getMessage)
      }
    } else {
      // Message to a remote RPC endpoint.
      //消息发送给远程节点
      postToOutbox(message.receiver, OneWayOutboxMessage(message.serialize(this)))
    }
  }
  
  //创建客户端 
  //TransportClient--用于获取预先协商的流的连续块的客户端。
  //此API旨在实现大量数据的高效传输,这些数据被分解为大小从数百KB到几MB不等的块。
  private[netty] def createClient(address: RpcAddress): TransportClient = {
    clientFactory.createClient(address.host, address.port)
  }
  
  //请求可终止的 当请求超过一定的时间 可以主动终止该请求
  //AbortableRpcFuture--由abort方法用来终止获取结果
  private[netty] def askAbortable[T: ClassTag](
      message: RequestMessage, timeout: RpcTimeout): AbortableRpcFuture[T] = {
    //Promise表示一种异步计算的结果。它可以被认为是一个容器,保存着那个未来将会被赋值的值。
    //Promise 提供了一种向 Future 对象进行赋值的方式。
    //通过 Promise,我们可以在计算完成后对其进行操作,获取或者显式地处理计算的结果。
    val promise = Promise[Any]()
    //远程节点地址
    val remoteAddr = message.receiver.address
    //消息 Option(选项)类型用来表示一个值是可选的(有值或无值)。
    var rpcMsg: Option[RpcOutboxMessage] = None
    
    //请求失败
    def onFailure(e: Throwable): Unit = {
      if (!promise.tryFailure(e)) {
        e match {
          case e : RpcEnvStoppedException => logDebug(s"Ignored failure: $e")
          case _ => logWarning(s"Ignored failure: $e")
        }
      }
    }
    
    //请求成功
    def onSuccess(reply: Any): Unit = reply match {
      //如果回复是失败 则抛出异常
      case RpcFailure(e) => onFailure(e)
      //如果尝试没成功则忽略该回复
      case rpcReply =>
        if (!promise.trySuccess(rpcReply)) {
          logWarning(s"Ignored message: $reply")
        }
    }
    
    //终止请求
    def onAbort(t: Throwable): Unit = {
      //先抛出异常
      onFailure(t)
      //调用abort方法终止请求
      rpcMsg.foreach(_.onAbort())
    }

    try {
      //如果目标地址是rpc环境 即同一环境发送请求
      if (remoteAddr == address) {
        //定义Promise用于获取结果
        val p = Promise[Any]()
        //onComplete函数是Future类中的一个回调函数,它接收一个PartialFunction作为参数,并在Future完成后执行这个回调函数。
        //onComplete函数会在Future完成后立即执行,无论成功或失败。
        //一旦future获取到结果之后立即执行
        p.future.onComplete {
          //如果成功获取到响应
          case Success(response) => onSuccess(response)
          //如果请求失败
          case Failure(e) => onFailure(e)
        }(ThreadUtils.sameThread)//调用方应确保在此“ExecutionContextExecutor”中运行的任务很短且从不阻塞。
        //发送消息
        dispatcher.postLocalMessage(message, p)
      } else {//如果是远程节点
        //初始化消息
        val rpcMessage = RpcOutboxMessage(message.serialize(this),
          onFailure,
          (client, response) => onSuccess(deserialize[Any](client, response)))
        rpcMsg = Option(rpcMessage)
        //将消息放到发件箱
        postToOutbox(message.receiver, rpcMessage)
        //如果发送失败则抛出异常
        promise.future.failed.foreach {
          case _: TimeoutException => rpcMessage.onTimeout()
          case _ =>
        }(ThreadUtils.sameThread)//调用方应确保在此“ExecutionContextExecutor”中运行的任务很短且从不阻塞。
      }
      
      //超时取消请求
      val timeoutCancelable = timeoutScheduler.schedule(new Runnable {
        override def run(): Unit = {
          val remoteRecAddr = if (remoteAddr == null) {//如果远程节点地址为空
            Try {
              //通过通道获取地址
              message.receiver.client.getChannel.remoteAddress()
            }.toOption.orNull
          } else {
            remoteAddr
          }
          //如果失败抛出异常
          onFailure(new TimeoutException(s"Cannot receive any reply from ${remoteRecAddr} " +
            s"in ${timeout.duration}"))
        }
      }, timeout.duration.toNanos, TimeUnit.NANOSECONDS)
      //取消之后立即执行
      promise.future.onComplete { v =>
        timeoutCancelable.cancel(true)
      }(ThreadUtils.sameThread)
    } catch {
      case NonFatal(e) =>
        onFailure(e)
    }
    //返回一个超时终止请求
    new AbortableRpcFuture[T](
      promise.future.mapTo[T].recover(timeout.addMessageIfTimeout)(ThreadUtils.sameThread),
      onAbort)
  }
  
  //请求远程节点 是可以终止的
  private[netty] def ask[T: ClassTag](message: RequestMessage, timeout: RpcTimeout): Future[T] = {
    askAbortable(message, timeout).future
  }

  //序列化内容
  private[netty] def serialize(content: Any): ByteBuffer = {
    javaSerializerInstance.serialize(content)
  }

   //返回将序列化的字节转发到“out”的[[SerializationStream]]。
   //用于写入序列化对象的流。
  private[netty] def serializeStream(out: OutputStream): SerializationStream = {
    javaSerializerInstance.serializeStream(out)
  }
  
  //反序列化
  private[netty] def deserialize[T: ClassTag](client: TransportClient, bytes: ByteBuffer): T = {
    NettyRpcEnv.currentClient.withValue(client) {
      deserialize { () =>
        javaSerializerInstance.deserialize[T](bytes)
      }
    }
  }
  
  //获取节点引用
  override def endpointRef(endpoint: RpcEndpoint): RpcEndpointRef = {
    dispatcher.getRpcEndpointRef(endpoint)
  }
  
  //关闭rpc环境
  override def shutdown(): Unit = {
    cleanup()
  }
  
  //使Dispatcher进程等待
  override def awaitTermination(): Unit = {
    dispatcher.awaitTermination()
  }

  private def cleanup(): Unit = {
    //如果是false 则更新为true
    //如果已经关闭则直接返回
    if (!stopped.compareAndSet(false, true)) {
      return
    }
    
    //遍历outboxes并移除每个发件箱
    val iter = outboxes.values().iterator()
    while (iter.hasNext()) {
      val outbox = iter.next()
      //移除该发件箱
      outboxes.remove(outbox.address)
      //停止该发件箱
      outbox.stop()
    }
    //停止超时调度器
    if (timeoutScheduler != null) {
      timeoutScheduler.shutdownNow()
    }
    //停止分发器
    if (dispatcher != null) {
      dispatcher.stop()
    }
    //停止文件服务器
    if (server != null) {
      server.close()
    }
    //停止客户端工厂
    if (clientFactory != null) {
      clientFactory.close()
    }
    //停止客户端连接执行器
    if (clientConnectionExecutor != null) {
      clientConnectionExecutor.shutdownNow()
    }
    //停止文件下载工厂
    if (fileDownloadFactory != null) {
      fileDownloadFactory.close()
    }
    //停止传输上下文环境
    if (transportContext != null) {
      transportContext.close()
    }
  }
  
  //反序列化
  override def deserialize[T](deserializationAction: () => T): T = {
    NettyRpcEnv.currentEnv.withValue(this) {
      deserializationAction()
    }
  }
  
  //文件服务器
  override def fileServer: RpcEnvFileServer = streamManager

  //打开读取通道  
  //ReadableByteChannel--可以读取字节的通道。
  override def openChannel(uri: String): ReadableByteChannel = {
    //初始化uri
    val parsedUri = new URI(uri)
    //断言 如果给定的uri 主机名 端口 路径有问题则抛出异常
    require(parsedUri.getHost() != null, "Host name must be defined.")
    require(parsedUri.getPort() > 0, "Port must be defined.")
    require(parsedUri.getPath() != null && parsedUri.getPath().nonEmpty, "Path must be defined.")
    
    //初始化管道
    val pipe = Pipe.open()
    //初始化管道来源
    val source = new FileDownloadChannel(pipe.source())
    //执行一个代码块并调用catch块中的失败回调。
    //如果异常发生在catch或finally块中,它们将被附加到原始异常中被抑制的异常列表中,然后重新抛出。
    Utils.tryWithSafeFinallyAndFailureCallbacks(block = {
      val client = downloadClient(parsedUri.getHost(), parsedUri.getPort())
      val callback = new FileDownloadCallback(pipe.sink(), source, client)
      client.stream(parsedUri.getPath(), callback)
    })(catchBlock = {
      //异常则关闭管道
      pipe.sink().close()
      source.close()
    })

    source
  }
  
  //下载客户端
  private def downloadClient(host: String, port: Int): TransportClient = {
    //如果文件下载工厂为空 则先创建工厂
    if (fileDownloadFactory == null) synchronized {
      if (fileDownloadFactory == null) {
        //模块
        val module = "files"
        //前缀
        val prefix = "spark.rpc.io."
        //克隆
        val clone = conf.clone()

        //复制spark.files命名空间中未重写的任何RPC配置。
        conf.getAll.foreach { case (key, value) =>
          if (key.startsWith(prefix)) {
            val opt = key.substring(prefix.length())
            clone.setIfMissing(s"spark.$module.io.$opt", value)
          }
        }
        
        //io线程
        val ioThreads = clone.getInt("spark.files.io.threads", 1)
        //下载配置
        val downloadConf = SparkTransportConf.fromSparkConf(clone, module, ioThreads)
        //下载上下文环境
        val downloadContext = new TransportContext(downloadConf, new NoOpRpcHandler(), true)
        //创建文件下载工厂
        fileDownloadFactory = downloadContext.createClientFactory(createClientBootstraps())
      }
    }
    //通过工厂创建下载客户端
    fileDownloadFactory.createClient(host, port)
  }
  
  //文件下载通道
  private class FileDownloadChannel(source: Pipe.SourceChannel) extends ReadableByteChannel {
    //
    @volatile private var error: Throwable = _

    def setError(e: Throwable): Unit = {
      //此setError回调由内部RPC线程调用,以便传播远程从该通道读取的应用程序级线程的异常。
      //当发生RPC错误,RPC系统将调用setError(),然后关闭管与“源”管道的另一端相对应的SinkChannel。
      //管道的关闭sink将导致“source.read()”操作返回EOF,从而取消阻止应用程序级别读取线程。
      //因此,在onError()回调,事实上,在这里调用它是危险的,因为close()将与read()调用异步,并可能触发竞争条件
      //导致数据损坏。有关此主题的更多详细信息,请参阅SPARK-22982的PR。
      error = e
    }

    override def read(dst: ByteBuffer): Int = {
      Try(source.read(dst)) match {
        //请参阅上面setError()中的文档:如果发生RPC错误,则setError()将被调用以传播RPC错误,然后“源”的相应
        //管道SinkChannel将关闭,取消阻止此读取。在这种情况下,我们想传播
        //远程RPC异常(而不是由管道关闭触发的任何异常,例如
        //ChannelClosedException),因此出现此`error!=空`check:
        case _ if error != null => throw error
        case Success(bytesRead) => bytesRead
        case Failure(readErr) => throw readErr
      }
    }
    
    //关闭读取数据源
    override def close(): Unit = source.close()
    //判断读取数据源是否打开
    override def isOpen(): Boolean = source.isOpen()

  }
  
  //文件下载回调
  private class FileDownloadCallback(
      //WritableByteChannel--可以写入字节的通道。
      //在任何给定时间,可写通道上只能进行一次写入操作。
      //如果一个线程在通道上启动写操作,那么尝试启动另一个写操作的任何其他线程都将阻塞,直到第一个操作完成。
      //其他类型的I/O操作是否可以与写入操作同时进行取决于通道的类型。
      sink: WritableByteChannel,
      //文件下载通道
      source: FileDownloadChannel,
      //传输客户端
      client: TransportClient) extends StreamCallback {//StreamCallback--流数据的回调
    
    //数据写入
    override def onData(streamId: String, buf: ByteBuffer): Unit = {
      while (buf.remaining() > 0) {
        sink.write(buf)
      }
    }
    
    //数据写入完成关闭写入通道
    override def onComplete(streamId: String): Unit = {
      sink.close()
    }
    
    //写入失败则打印错误原因并关闭写入通道
    override def onFailure(streamId: String, cause: Throwable): Unit = {
      source.setError(cause)
      sink.close()
    }

  }
}

private[netty] object NettyRpcEnv extends Logging {
   //当反序列化[[NettyRpcEndpointRef]]时,它需要对[[NettyRpcEnv]]的引用。
   //使用“currentEnv”包装反序列化代码。例如。,
   //当前RpcEnv环境
  private[netty] val currentEnv = new DynamicVariable[NettyRpcEnv](null)

   //与“currentEnv”类似,此变量引用与RPC关联的客户端实例,以防在反序列化过程中需要查找远程地址。
  private[netty] val currentClient = new DynamicVariable[TransportClient](null)

}

private[rpc] class NettyRpcEnvFactory extends RpcEnvFactory with Logging {
  
  //初始化创建RpcEnv环境
  def create(config: RpcEnvConfig): RpcEnv = {
    val sparkConf = config.conf
    //在多个线程中使用JavaSerializerInstance是安全的。
    //然而,如果我们计划在未来支持KryoSerializer,我们必须使用ThreadLocal来存储SerializerInstance
    val javaSerializerInstance =
      new JavaSerializer(sparkConf).newInstance().asInstanceOf[JavaSerializerInstance]
    val nettyEnv =
      new NettyRpcEnv(sparkConf, javaSerializerInstance, config.advertiseAddress,
        config.securityManager, config.numUsableCores)
    if (!config.clientMode) {
      //函数 参数是Int 返回值是(NettyRpcEnv, Int) {}中的是方法体
      val startNettyRpcEnv: Int => (NettyRpcEnv, Int) = { actualPort =>
        nettyEnv.startServer(config.bindAddress, actualPort)
        (nettyEnv, nettyEnv.address.port)
      }
      try {
        //尝试在给定端口上启动服务,或者多次尝试后失败。
        //每次后续尝试使用1+上一次尝试中使用的端口(除非端口为0)。
        Utils.startServiceOnPort(config.port, startNettyRpcEnv, sparkConf, config.name)._1
      } catch {//启动失败则抛出异常
        case NonFatal(e) =>
          nettyEnv.shutdown()
          throw e
      }
    }
    nettyEnv
  }
}

 //RpcEndpointRef的NettyRpcEnv版本。
 //这个类的行为因创建位置而异。在“拥有”RpcEndpoint的节点上,它是围绕RpcEndpointAddress实例的简单包装器。
 //在接收引用的序列化版本的其他计算机上,行为会发生变化。
 //该实例将跟踪发送引用的TransportClient,以便通过客户端连接向端点发送消息,而不需要打开新的连接。
 //此引用的RpcAddress可以为null;这意味着ref只能通过客户端连接使用,因为承载端点的进程没有监听传入的连接。
 //这些引用不应与第三方共享,因为它们将无法向端点发送消息。
private[netty] class NettyRpcEndpointRef(
    //transient是Scala中的一个关键字,用于修饰类的成员变量。它指示编译器在序列化对象时忽略被修饰的变量
    @transient private val conf: SparkConf,
    //节点地址
    private val endpointAddress: RpcEndpointAddress,
    //既不被序列化也有可能丢失
    @transient @volatile private var nettyEnv: NettyRpcEnv) extends RpcEndpointRef(conf) {
  //TransportClient--传输客户端
  @transient @volatile var client: TransportClient = _
  
  //远程节点地址
  override def address: RpcAddress =
    if (endpointAddress.rpcAddress != null) endpointAddress.rpcAddress else null

  //从流中读取字段
  private def readObject(in: ObjectInputStream): Unit = {
    //从该流中读取当前类的非静态和非瞬态字段。这只能从正在反序列化的类的readObject方法调用。
    //如果以其他方式调用它,它将抛出NotActiveException。
    in.defaultReadObject()
    nettyEnv = NettyRpcEnv.currentEnv.value
    client = NettyRpcEnv.currentClient.value
  }
  
  //将字段写入到流中
  private def writeObject(out: ObjectOutputStream): Unit = {
    //将当前类的非静态和非瞬态字段写入该流。这只能从正在序列化的类的writeObject方法调用。
    //如果以其他方式调用它,它将抛出NotActiveException。
    out.defaultWriteObject()
  }

  //节点名称
  override def name: String = endpointAddress.name
  
  //请求可终止
  override def askAbortable[T: ClassTag](
      message: Any, timeout: RpcTimeout): AbortableRpcFuture[T] = {
    nettyEnv.askAbortable(new RequestMessage(nettyEnv.address, this, message), timeout)
  }
  
  //发送请求
  override def ask[T: ClassTag](message: Any, timeout: RpcTimeout): Future[T] = {
    askAbortable(message, timeout).future
  }
  
  //发消息
  override def send(message: Any): Unit = {
    require(message != null, "Message is null")
    nettyEnv.send(new RequestMessage(nettyEnv.address, this, message))
  }
  
  //重写toString方法
  override def toString: String = s"NettyRpcEndpointRef(${endpointAddress})"

  //重写equals方法
  final override def equals(that: Any): Boolean = that match {
    case other: NettyRpcEndpointRef => endpointAddress == other.endpointAddress
    case _ => false
  }
  
  //重写hashCode方法
  final override def hashCode(): Int =
    if (endpointAddress == null) 0 else endpointAddress.hashCode()
}

 //从发送方发送给接收方的消息。
private[netty] class RequestMessage(
    val senderAddress: RpcAddress,//发送者地址
    val receiver: NettyRpcEndpointRef,//接收者 接收节点的引用
    val content: Any) {//消息内容

  //手动序列化[[RequestMessage]]以最小化大小。
  def serialize(nettyEnv: NettyRpcEnv): ByteBuffer = {
    //提供一种零拷贝方式将ByteArrayOutputStream中的数据转换为ByteBuffer
    val bos = new ByteBufferOutputStream()
    //创建一个新的数据输出流,以将数据写入指定的基础输出流。写入的计数器设置为零。
    val out = new DataOutputStream(bos)
    try {
      //将发送者的地址写入到输出流中
      writeRpcAddress(out, senderAddress)
      //将接收者的地址写入到输出流中
      writeRpcAddress(out, receiver.address)
      //将接收者的名称写入输出流中
      out.writeUTF(receiver.name)
      //返回将序列化的字节转发到“out”的[[SerializationStream]]。
      val s = nettyEnv.serializeStream(out)
      try {
        //将内容写入到流中
        s.writeObject(content)
      } finally {
        s.close()
      }
    } finally {
      out.close()
    }
    bos.toByteBuffer
  }

  private def writeRpcAddress(out: DataOutputStream, rpcAddress: RpcAddress): Unit = {
    //地址为空则写入0
    if (rpcAddress == null) {
      //将布尔值作为1字节值写入基础输出流。值true被写成值(字节)1;值false被写成值(字节)0。如果没有引发异常,则写入的计数器将递增1。
      out.writeBoolean(false)
    } else {
      //写入1
      out.writeBoolean(true)
      //以独立于机器的方式,使用修改后的UTF-8编码将字符串写入基础输出流。
      out.writeUTF(rpcAddress.host)
      //将一个int以四个字节的形式写入基础输出流,高位字节优先。如果没有引发异常,则写入的计数器将增加4。
      out.writeInt(rpcAddress.port)
    }
  }
  
  //重写toString方法
  override def toString: String = s"RequestMessage($senderAddress, $receiver, $content)"
}

//类RequestMessage的单例对象
private[netty] object RequestMessage {
  
  //从输入流中读取地址
  private def readRpcAddress(in: DataInputStream): RpcAddress = {
    //从输入流中能否读取到内容
    val hasRpcAddress = in.readBoolean()
    //如果能读取到
    if (hasRpcAddress) {
      //通过主机名和端口生成地址
      RpcAddress(in.readUTF(), in.readInt())
    } else {
      null
    }
  }
  
  //生成请求消息
  def apply(nettyEnv: NettyRpcEnv, client: TransportClient, bytes: ByteBuffer): RequestMessage = {
    //从ByteBuffer读取数据。
    val bis = new ByteBufferInputStream(bytes)
    //创建使用指定的基础InputStream的DataInputStream。
    val in = new DataInputStream(bis)
    try {
      //从输入流中读取发送者地址
      val senderAddress = readRpcAddress(in)
      //从输入流中读取接收者地址
      val endpointAddress = RpcEndpointAddress(readRpcAddress(in), in.readUTF())
      /创建接收者的引用
      val ref = new NettyRpcEndpointRef(nettyEnv.conf, endpointAddress, nettyEnv)
      //设置引用中的传输客户端
      ref.client = client
      //生成请求消息
      new RequestMessage(
        senderAddress,
        ref,
        // The remaining bytes in `bytes` are the message content.
        //“字节”中的其余字节是消息内容。
        nettyEnv.deserialize(client, bytes))
    } finally {
      in.close()
    }
  }
}

 //指示消息接收者侧发生某种故障的响应。
private[netty] case class RpcFailure(e: Throwable)

 //将传入的RPC分派到已注册的endpoint节点。
 //处理程序跟踪与其通信的所有客户端实例,
 //以便RpcEnv知道在向客户端端点发送RPC时要使用哪个“TransportClient”实例(即,不侦听传入连接,而是需要通过客户端套接字联系的端点)。
 //事件是在每个连接的基础上发送的,因此,如果客户端打开到RpcEnv的多个连接,则会为该客户端创建多个连接/断开连接事件(尽管“RpcAddress”信息不同)。
private[netty] class NettyRpcHandler(
    dispatcher: Dispatcher,//消息分发器
    nettyEnv: NettyRpcEnv,//RpcEnv环境
    //流管理器
    streamManager: StreamManager) extends RpcHandler with Logging {

  // A variable to track the remote RpcEnv addresses of all clients
  //用于跟踪所有客户端的远程RpcEnv地址的变量
  private val remoteAddresses = new ConcurrentHashMap[RpcAddress, RpcAddress]()

  //处理收到的消息 需要回复
  override def receive(
      //传输客户端
      client: TransportClient,
      //消息
      message: ByteBuffer,
      //单个RPC的结果的回调。无论成功与否,都会调用一次。
      callback: RpcResponseCallback): Unit = {
    //生成待发送的消息
    val messageToDispatch = internalReceive(client, message)
    //dispatcher给内部节点发消息 需要回复
    dispatcher.postRemoteMessage(messageToDispatch, callback)
  }

  ////处理收到的消息 不需要回复
  override def receive(
      //传输客户端
      client: TransportClient,
      //消息
      message: ByteBuffer): Unit = {
    //生成待发送的消息
    val messageToDispatch = internalReceive(client, message)
    //dispatcher给内部节点发单向消息 不需要回复
    dispatcher.postOneWayMessage(messageToDispatch)
  }
  
  //内部接收
  private def internalReceive(client: TransportClient, message: ByteBuffer): RequestMessage = {
    //获取发送者的地址
    val addr = client.getChannel().remoteAddress().asInstanceOf[InetSocketAddress]
    //断言 如果地址为空则抛出异常
    assert(addr != null)
    //客户端地址
    val clientAddr = RpcAddress(addr.getHostString, addr.getPort)
    //请求消息
    val requestMessage = RequestMessage(nettyEnv, client, message)
    //如果发送者地址为空
    if (requestMessage.senderAddress == null) {
      //使用客户端的套接字地址作为发件人创建一条新消息。
      new RequestMessage(clientAddr, requestMessage.receiver, requestMessage.content)
    } else {
      //远程RpcEnv侦听某个端口,我们还应该为侦听地址激发RemoteProcessConnected
      val remoteEnvAddress = requestMessage.senderAddress
      //putIfAbsent--如果指定的键尚未与值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
      //返回:与指定键关联的上一个值,如果没有键的映射,则为null
      if (remoteAddresses.putIfAbsent(clientAddr, remoteEnvAddress) == null) {
        //给所有的节点发消息远程进程已经连接
        dispatcher.postToAll(RemoteProcessConnected(remoteEnvAddress))
      }
      requestMessage
    }
  }
  
  //获取流管理器
  override def getStreamManager: StreamManager = streamManager
  
  //捕捉到的传输客户端的异常
  override def exceptionCaught(cause: Throwable, client: TransportClient): Unit = {
    //获取发送者的地址
    val addr = client.getChannel.remoteAddress().asInstanceOf[InetSocketAddress]
    if (addr != null) {
      //生成客户端地址
      val clientAddr = RpcAddress(addr.getHostString, addr.getPort)
      //dispatcher向所有节点发送远程进程连接异常
      dispatcher.postToAll(RemoteProcessConnectionError(cause, clientAddr))
      //如果remove-RpcEnv侦听某个地址,我们还应该激发
      //远程RpcEnv侦听地址的RemoteProcessConnectionError
      //获取远程Env环境的地址
      val remoteEnvAddress = remoteAddresses.get(clientAddr)
      if (remoteEnvAddress != null) {
        //向远程Env的所有节点发送连接异常消息
        dispatcher.postToAll(RemoteProcessConnectionError(cause, remoteEnvAddress))
      }
    } else {
      //如果通道在连接前关闭,其remoteAddress将为null。请参阅java.net.Socket.getRemoteSocketAddress
      //因为我们无法获取RpcAddress,所以只需记录它
      logError("Exception before connecting to the client", cause)
    }
  }
  
  //活动的进程
  override def channelActive(client: TransportClient): Unit = {
    //获取地址
    val addr = client.getChannel().remoteAddress().asInstanceOf[InetSocketAddress]
    //断言 地址为空则抛出异常
    assert(addr != null)
    //生成客户端地址
    val clientAddr = RpcAddress(addr.getHostString, addr.getPort)
    //向所有节点发送消息远程进程已连接
    dispatcher.postToAll(RemoteProcessConnected(clientAddr))
  }

  //不活动的进程
  override def channelInactive(client: TransportClient): Unit = {
    //获取地址
    val addr = client.getChannel.remoteAddress().asInstanceOf[InetSocketAddress]
    if (addr != null) {
      //客户端地址
      val clientAddr = RpcAddress(addr.getHostString, addr.getPort)
      //移除该客户端的发件箱
      nettyEnv.removeOutbox(clientAddr)
      //向所有节点发送消息远程进程已断开连接
      dispatcher.postToAll(RemoteProcessDisconnected(clientAddr))
      //远程Env环境的地址
      val remoteEnvAddress = remoteAddresses.remove(clientAddr)
      //如果remove-RpcEnv侦听某个地址,我们还应该激发
      //远程RpcEnv侦听地址的RemoteProcessDisconnected
      if (remoteEnvAddress != null) {
        //如果远程Env地址不为空 则向其所有节点发送进程已经断开连接
        dispatcher.postToAll(RemoteProcessDisconnected(remoteEnvAddress))
      }
    } else {
      //如果通道在连接前关闭,其remoteAddress将为null。在这种情况下,我们可以忽略它,因为我们不会解雇“关联”。
      //请参阅java.net.Socket.getRemoteSocketAddress
    }
  }
}

NettyRpcEnv类: 主要包括传输配置、消息分发器dispatcher、客户端工厂clientFactory用于批量获取客户端,传输服务器server用于进行文件传输的服务器,消息发件箱outboxes管理需要发送的消息,通过目标地址和Outbox对应:

//一个关于RpcAddress和Outbox的映射,当我们准备连接到一个远程地址时,只需要将消息放到OutBox里面
   //进而实现一种非阻塞的发送方式
   //发件箱
  private val outboxes = new ConcurrentHashMap[RpcAddress, Outbox]()

通过消息分发器注册节点:

//设置endpoint节点
  override def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef = {
    dispatcher.registerRpcEndpoint(name, endpoint)
  }

将消息放到发件箱:

//发消息到发件箱 消息接收者是节点的引用 
  private def postToOutbox(receiver: NettyRpcEndpointRef, message: OutboxMessage): Unit = {
    if (receiver.client != null) {
      //引用客户端不为空则将消息发给发送客户端
      message.sendWith(receiver.client)
    } else {
      //如果目标发件箱为空 则新建一个
      //断言 如果目标地址为空则抛出异常
      require(receiver.address != null,
        "Cannot send message to client endpoint with no listen address.")
      val targetOutbox = {
        //获取发件箱
        val outbox = outboxes.get(receiver.address)
        if (outbox == null) {
          //创建发件箱
          val newOutbox = new Outbox(this, receiver.address)
          //不管该地址绑定的什么改为绑定新的发件箱
          //putIfAbsent--如果指定的键尚未与值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
          val oldOutbox = outboxes.putIfAbsent(receiver.address, newOutbox)
          if (oldOutbox == null) {
            newOutbox
          } else {
            oldOutbox
          }
        } else {
          outbox
        }
      }
      //如果环境停止了
      if (stopped.get) {
        // It's possible that we put `targetOutbox` after stopping. So we need to clean it.
        //移除该发件箱
        outboxes.remove(receiver.address)
        //停止该发件箱
        targetOutbox.stop()
      } else {
        //发件箱向对应的节点发消息 添加消息到消息循环里面
        targetOutbox.send(message)
      }
    }
  }

需要注意的是消息接收者是对应节点的引用NettyRpcEndpointRef,也就是说消息具体是通过引用来发送的,与之前讲到的引用对应。

//发消息
  private[netty] def send(message: RequestMessage): Unit = {
    //目标地址
    val remoteAddr = message.receiver.address
    //如果目标地址是rpc环境 即本地发送消息
    if (remoteAddr == address) {
      // Message to a local RPC endpoint.
      try {
        //由消息分发器发送该条单向消息
        dispatcher.postOneWayMessage(message)
      } catch {
        //如果rpc环境异常则打印该异常
        case e: RpcEnvStoppedException => logDebug(e.getMessage)
      }
    } else {
      // Message to a remote RPC endpoint.
      //消息发送给远程节点
      postToOutbox(message.receiver, OneWayOutboxMessage(message.serialize(this)))
    }
  }

如果是同一个Env中的节点之间发送消息则通过dispatcher传递即可,如果是外部的远程节点则通过postToOutbox来发送。

NettyRpcEndpointRef类: 重写RpcEndpointRef中的发送方法,同时在需要回复的方法中加入Future来获取请求结果,针对容易超时的请求设置请求中断。

NettyRpcHandler类: 传入消息处理句柄,通过recive方法将收到的消息打包并交由消息分发器处理。

总结:在源码阅读过程中有一些比较简单的是直接通过翻译器翻译的,有的则是结合上下文翻译的,有的还对用到的Scala知识点做了注解,如果能全部仔细看完,一定会有所收获的,有理解错误的地方欢迎指正,到目前为止原理源码阅读完毕,后续会通过具体的例子来结合讲解。