Spark源码阅读篇-Rpc通信-Outbox发件箱

63 阅读8分钟

上一节介绍了Inbox收件箱,这一节来进一步阅读Outbox发件箱的源码。

Outbox源码

//sealed限制继承层级
//消息发件箱
private[netty] sealed trait OutboxMessage {

  def sendWith(client: TransportClient): Unit

  def onFailure(e: Throwable): Unit

}

//单向消息发件箱
private[netty] case class OneWayOutboxMessage(content: ByteBuffer) extends OutboxMessage
  with Logging {

  override def sendWith(client: TransportClient): Unit = {
    //向服务器端的RpcHandler发送不透明消息。该消息不需要任何回复,也不提供任何交付保证。
    client.send(content)
  }
  
  //发送失败打印异常信息
  override def onFailure(e: Throwable): Unit = {
    e match {
      case e1: RpcEnvStoppedException => logDebug(e1.getMessage)
      case e1: Throwable => logWarning(s"Failed to send one-way RPC.", e1)
    }
  }

}

//消息发件箱 有回复的
private[netty] case class RpcOutboxMessage(
    content: ByteBuffer,
    _onFailure: (Throwable) => Unit,
    _onSuccess: (TransportClient, ByteBuffer) => Unit)
  extends OutboxMessage with RpcResponseCallback with Logging {
 
  //_表示默认值
  //传输客户端
  private var client: TransportClient = _
  //请求ID
  private var requestId: Long = _

  override def sendWith(client: TransportClient): Unit = {
    this.client = client
    //向服务器端的RpcHandler发送不透明消息。回调将与服务器的响应一起调用,或者在出现任何故障时调用。
    this.requestId = client.sendRpc(content, this)
  }

  private[netty] def removeRpcRequest(): Unit = {
    if (client != null) {
      //删除与给定RPC关联的任何状态。
      client.removeRpcRequest(requestId)
    } else {
      logError("Ask terminated before connecting successfully")
    }
  }
  
  //超时
  def onTimeout(): Unit = {
    removeRpcRequest()
  }
  
  //终止
  def onAbort(): Unit = {
    removeRpcRequest()
  }
  
  //失败
  override def onFailure(e: Throwable): Unit = {
    _onFailure(e)
  }
  
  //成功
  override def onSuccess(response: ByteBuffer): Unit = {
    _onSuccess(client, response)
  }

}

//发件箱
private[netty] class Outbox(nettyEnv: NettyRpcEnv, val address: RpcAddress) {

  outbox => //给它一个别名,这样我们就可以在闭包中更清楚地使用它。
  
  //修饰变量 使用之前需要先获取锁
  //消息列表 每个结点都是消息发件箱
  @GuardedBy("this")
  private val messages = new java.util.LinkedList[OutboxMessage]
  
  //发送客户端
  @GuardedBy("this")
  private var client: TransportClient = null

   //connectFuture指向连接任务。如果没有连接任务,那么connectFuture将为null。
  @GuardedBy("this")
  private var connectFuture: java.util.concurrent.Future[Unit] = null
  
  //状态 默认没停止
  @GuardedBy("this")
  private var stopped = false

   //如果有任何线程正在耗尽消息队列
  @GuardedBy("this")
  private var draining = false

   //发送消息。如果没有活动连接,请缓存它并启动新连接。如果[[Outbox]]已停止,则会以[[SparkException]]通知发件人。
  def send(message: OutboxMessage): Unit = {
    val dropped = synchronized {
      //如果发件箱停止
      if (stopped) {
        true
      } else {
        //将消息放入链表
        messages.add(message)
        false
      }
    }
    if (dropped) {
      //消息发送失败 因为发件箱停止运行了
      message.onFailure(new SparkException("Message is dropped because Outbox is stopped"))
    } else {
      //清空消息队列
      drainOutbox()
    }
  }

   //清空消息队列。如果还有其他排水线程,请退出。
   //如果尚未建立连接,请在“nettyEnv.clientConnectionExecutor”中启动任务以设置连接。
  private def drainOutbox(): Unit = {
    var message: OutboxMessage = null
    synchronized {
      //如果发件箱停止了 直接返回
      if (stopped) {
        return
      }
      if (connectFuture != null) {
        //我们正在连接到远程地址,所以退出即可
        return
      }
      if (client == null) {
        //没有连接任务,但客户端为null,因此我们需要启动连接任务。
        //启动连接任务
        launchConnectTask()
        return
      }
      //如果发件箱消息正在被发送 则退出
      if (draining) {
        return
      }
      //取第一个结点的值
      message = messages.poll()
      //如果消息为空则返回
      if (message == null) {
        return
      }
      //表示消息正在被处理
      draining = true
    }
    while (true) {
      try {
        //发送客户端
        val _client = synchronized { client }
        if (_client != null) {
          //如果客户端不为空则通过该客户端发送消息
          message.sendWith(_client)
        } else {
          //如果发件箱运行中则抛出异常
          assert(stopped)
        }
      } catch {//抛出异常
        case NonFatal(e) =>
          handleNetworkFailure(e)
          return
      }
      synchronized {
        //如果发件箱停止则返回
        if (stopped) {
          return
        }
        //取出消息
        message = messages.poll()
        //如果消息为空则返回
        if (message == null) {
          draining = false
          return
        }
      }
    }
  }
  
  //启动连接任务
  private def launchConnectTask(): Unit = {
    connectFuture = nettyEnv.clientConnectionExecutor.submit(new Callable[Unit] {

      override def call(): Unit = {
        try {
          //生成客户端
          val _client = nettyEnv.createClient(address)
          outbox.synchronized {
            client = _client
            //如果发件箱停止则关闭客户端
            if (stopped) {
              closeClient()
            }
          }
        } catch {//抛出异常
          case ie: InterruptedException =>
            return
          case NonFatal(e) =>
            outbox.synchronized { connectFuture = null }
            handleNetworkFailure(e)
            return
        }
        outbox.synchronized { connectFuture = null }
        //现在可能没有线程在处理消息队列。如果我们不在这里耗尽,我们就无法发送消息,直到下一条消息到达。
        drainOutbox()
      }
    })
  }

   //停止[[Inbox]]并将原因通知等待的邮件。
  private def handleNetworkFailure(e: Throwable): Unit = {
    synchronized {
      //断言 如果连接不为空则抛出异常
      assert(connectFuture == null)
      //如果发件箱停止则返回
      if (stopped) {
        return
      }
      //将发件箱状态设置成停止
      stopped = true
      //关闭客户端
      closeClient()
    }
    //从nettyEnv中删除此发件箱,以便其他邮件将创建一个新的发件箱和一个新连接 一个地址对应一个发件箱
    nettyEnv.removeOutbox(address)

    //通知剩余消息的连接失败
    //我们总是在更新消息之前检查“stopped”,所以在这里我们可以确保没有线程会更新消息,并且只排干队列是安全的。
    var message = messages.poll()
    while (message != null) {
      //取出每一个消息 并通知消息发送方消息发送失败
      message.onFailure(e)
      message = messages.poll()
    }
    //如果消息不为空则抛出异常
    assert(messages.isEmpty)
  }
  
  //关闭客户端
  private def closeClient(): Unit = synchronized {
    // Just set client to null. Don't close it in order to reuse the connection.
    //只需将客户端设置为null。不要为了重新使用连接而关闭它。
    client = null
  }

   //停止[[发件箱]]。[[Outbox]]中的其余消息将通过[[SparkException]]进行通知。
  def stop(): Unit = {
    synchronized {
      //如果发件箱停止则返回
      if (stopped) {
        return
      }
      //将发件箱状态设置成停止
      stopped = true
      //如果连接不为空则抛出异常
      if (connectFuture != null) {
        //停止连接
        connectFuture.cancel(true)
      }
      //关闭客户端
      closeClient()
    }

    //我们总是在更新消息之前检查“stopped”,所以在这里我们可以确保没有线程会更新消息,并且只排干队列是安全的。
    var message = messages.poll()
    while (message != null) {
      //取出每一个消息 并通知消息发送方消息发送失败
      message.onFailure(new SparkException("Message is dropped because Outbox is stopped"))
      message = messages.poll()
    }
  }
}

Outbox中首先定义了消息特质OutboxMessage,自带发送消息方法sendWith和发送失败抛出异常onFailure方法,然后定义单向发件箱消息OneWayOutboxMessage继承OutboxMessage,定义有回复的rpc发件箱消息RpcOutboxMessage,该消息中还指定具体发送消息的客户端client,类型是传输客户端TransportClient,还包括消息发送状态超时、终止、成功等状态方法。

 //超时
  def onTimeout(): Unit = {
    removeRpcRequest()
  }
  
  //终止
  def onAbort(): Unit = {
    removeRpcRequest()
  }
  
  //失败
  override def onFailure(e: Throwable): Unit = {
    _onFailure(e)
  }
  
  //成功
  override def onSuccess(response: ByteBuffer): Unit = {
    _onSuccess(client, response)
  }

Outbox中通过链表messages来存储消息,每个节点都是一个OutboxMessage,messages通过@GuardedBy("this")来修饰,每次使用之前都需要先获取messages的锁,确保线程安全:

//修饰变量 使用之前需要先获取锁
  //消息列表 每个结点都是消息发件箱
  @GuardedBy("this")
  private val messages = new java.util.LinkedList[OutboxMessage]

同时指定消息发送客户端:

 //发送客户端
  @GuardedBy("this")
  private var client: TransportClient = null

send负责发送消息,先检查发件箱是否停止,没有就将消息放进messages链表中,然后开始执行drainOutbox消费messages中的消息:

//发送消息。如果没有活动连接,请缓存它并启动新连接。如果[[Outbox]]已停止,则会以[[SparkException]]通知发件人。
  def send(message: OutboxMessage): Unit = {
    val dropped = synchronized {
      //如果发件箱停止
      if (stopped) {
        true
      } else {
        //将消息放入链表
        messages.add(message)
        false
      }
    }
    if (dropped) {
      //消息发送失败 因为发件箱停止运行了
      message.onFailure(new SparkException("Message is dropped because Outbox is stopped"))
    } else {
      //清空消息队列
      drainOutbox()
    }
  }

drainOutbox先判断发件箱状态和客户端是否为空,如果发件箱停止运行则直接返回,如果客户端为空则启动连接任务,然后正式开始处理消息,先获取客户端,然后messages链表的第一个节点的值,然后调用具体消息的sendWith方法通过该客户端发送该消息,发送过程如遇异常则抛出,然后接着获取下一个消息,如果获取的消息是空则停止,否则循环处理链表中的消息,指导处理完毕。

//清空消息队列。如果还有其他排水线程,请退出。
   //如果尚未建立连接,请在“nettyEnv.clientConnectionExecutor”中启动任务以设置连接。
  private def drainOutbox(): Unit = {
    var message: OutboxMessage = null
    synchronized {
      //如果发件箱停止了 直接返回
      if (stopped) {
        return
      }
      if (connectFuture != null) {
        // We are connecting to the remote address, so just exit
        //我们正在连接到远程地址,所以退出即可
        return
      }
      if (client == null) {
        // There is no connect task but client is null, so we need to launch the connect task.
        //没有连接任务,但客户端为null,因此我们需要启动连接任务。
        //启动连接任务
        launchConnectTask()
        return
      }
      //如果发件箱消息正在被发送 则退出
      if (draining) {
        // There is some thread draining, so just exit
        return
      }
      //取第一个结点的值
      message = messages.poll()
      //如果消息为空则返回
      if (message == null) {
        return
      }
      //表示消息正在被处理
      draining = true
    }
    while (true) {
      try {
        //发送客户端
        val _client = synchronized { client }
        if (_client != null) {
          //如果客户端不为空则通过该客户端发送消息
          message.sendWith(_client)
        } else {
          //如果发件箱运行中则抛出异常
          assert(stopped)
        }
      } catch {//抛出异常
        case NonFatal(e) =>
          handleNetworkFailure(e)
          return
      }
      synchronized {
        //如果发件箱停止则返回
        if (stopped) {
          return
        }
        //取出消息
        message = messages.poll()
        //如果消息为空则返回
        if (message == null) {
          draining = false
          return
        }
      }
    }
  }

总结:Outbox中定义了不同类型的消息,消息中自带发送方法,只需要传入客户端即可,然后定义了发件箱,发件箱中通过链表来管理需要发送的消息,循环扫描处理需要发送的消息。