Spark源码阅读篇-Rpc通信-RpcEndpoint、RpcEndpointRef和RpcEnv

144 阅读10分钟

上一节介绍了Spark Rpc通信中主要的架构模型和涉及到的类和对象的名词解释,接下来正式开始看源码,本节主要是看RpcEnv、RpcEndpoint和RpcEndpointRef的源码,其中RpcEnv和RpcEndpointRef类似于抽象接口,在netty包下面都有具体的netty实现,RpcEndpoint虽然也是包含抽象的接口,但是具体的实现根据本身角色不同实现方式不一样。

RpcEnv源码

//RpcEnv实现必须有一个带有空构造函数的[[RpcEnvFactory]]实现,这样才能通过反射创建它。
private[spark] object RpcEnv {

  def create(
      name: String,
      host: String,
      port: Int,
      conf: SparkConf,
      securityManager: SecurityManager,
      clientMode: Boolean = false): RpcEnv = {
    create(name, host, host, port, conf, securityManager, 0, clientMode)
  }
  
  //这里修改了父类的构造函数
  def create(
      name: String,
      bindAddress: String,
      advertiseAddress: String,
      port: Int,
      conf: SparkConf,
      securityManager: SecurityManager,
      numUsableCores: Int,
      clientMode: Boolean): RpcEnv = {
    val config = RpcEnvConfig(conf, name, bindAddress, advertiseAddress, port, securityManager,
      numUsableCores, clientMode)
    //通过工厂实现
    new NettyRpcEnvFactory().create(config)
  }
}

 //RPC环境。[[RpcEndpoint]]需要带着名称注册到RpcEnv中,实现接收消息。
 //然后[[RpcEnv]]将处理从[[RpcEndpointRef]]或远程节点发送的消息,并将它们传递到相应的[[RpcEndpoint]]。
 //对于没有被节点捕捉到的异常,将由RpcEnv捕获,[[RpcEnviv]]将使用[[RpcCallContext.sendFailure]]将异常发送回发送方,
 //或者在没有此类发送方或“NotSerializableException”的情况下将其记录下来。
private[spark] abstract class RpcEnv(conf: SparkConf) {

  //默认查找超时时间 从配置中获取
  private[spark] val defaultLookupTimeout = RpcUtils.lookupRpcTimeout(conf)

   //返回已经注册的节点RpcEndpoint的引用RpcEndpointRef 
   //将用于实现[[RpcEndpoint.self]]。如果相应的[[RpcEndpointRef]]不存在,则返回“null”。
  private[rpc] def endpointRef(endpoint: RpcEndpoint): RpcEndpointRef

   //返回RpcEnv正在监听的地址
  def address: RpcAddress

   //注册一个带名字的节点RpcEndpoint和它的引用RpcEndpointRef,RpcEnv不保证该方法是线程安全的
  def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef

   //异步检索由“uri”表示的[[RpcEndpointRef]]。
  def asyncSetupEndpointRefByURI(uri: String): Future[RpcEndpointRef]

   //检索由`uri`表示的[[RpcEndpointRef]]。这是一个阻塞动作。
  def setupEndpointRefByURI(uri: String): RpcEndpointRef = {
    //通过异步检索方式实现
    defaultLookupTimeout.awaitResult(asyncSetupEndpointRefByURI(uri))
  }

   //检索由“address”和“endpointName”表示的[[RpcEndpointRef]]。这是一个阻塞动作。
  def setupEndpointRef(address: RpcAddress, endpointName: String): RpcEndpointRef = {
    //通过地址和节点名称生成uri 然后再检索
    setupEndpointRefByURI(RpcEndpointAddress(address, endpointName).toString)
  }

   //停止“endpoint”指定的[[RpcEndpoint]]。
  def stop(endpoint: RpcEndpointRef): Unit

   //异步关闭此[[RpcEnv]]。如果需要确保[[RpcEnv]]成功退出,请在[[shutdown()]]之后立即调用[[awaitTermination()]]。
  def shutdown(): Unit

   //等待直到RpcEnv退出
  def awaitTermination(): Unit

   //没有[[RpcEnv]],就无法反序列化[[RpcEndpointRef]]。
   //因此,当反序列化任何包含[[RpcEndpointRef]]的对象时,反序列化代码应该用这个方法包装。
  def deserialize[T](deserializationAction: () => T): T

   //返回用于提供文件的文件服务器的实例。如果RpcEnv未在服务器模式下运行,则这可能为“null”。
  def fileServer: RpcEnvFileServer

   //打开一个通道以从给定的URI下载文件。如果RpcEnvFileServer返回的URI使用“spark”方案,则Utils类将调用此方法来检索文件。
  def openChannel(uri: String): ReadableByteChannel
}

 //一个RpcEnv服务器 服务器上的文件给应用程序处理
 //文件服务器可以返回由公共库(如“http”或“hdfs”)处理的URI,也可以返回由“RpcEnv#fetchFile'”处理的“spark”URI。
private[spark] trait RpcEnvFileServer {

   //添加要由此RpcEnv提供服务的文件。当文件存储在driver的本地文件系统中时,它用于将文件从driver提供给executor。
  def addFile(file: File): String

   //添加一个由此RpcEnv提供服务的jar包。类似于“addFile”,但适用于使用“SparkContext.addJar”添加的jar。
  def addJar(file: File): String

   //添加要通过此文件服务器提供服务的本地目录。
  def addDirectory(baseUri: String, path: File): String

  /** Validates and normalizes the base URI for directories. */
  //验证并规范化目录的基本URI。
  protected def validateDirectoryUri(baseUri: String): String = {
    val fixedBaseUri = "/" + baseUri.stripPrefix("/").stripSuffix("/")
    require(fixedBaseUri != "/files" && fixedBaseUri != "/jars",
      "Directory URI cannot be /files nor /jars.")
    fixedBaseUri
  }

}

//RpcEnv配置 不可变且具有模式匹配能力
private[spark] case class RpcEnvConfig(
    conf: SparkConf,
    name: String,
    bindAddress: String,
    advertiseAddress: String,
    port: Int,
    securityManager: SecurityManager,
    numUsableCores: Int,
    clientMode: Boolean)

一个RpcEnv是由NettyRpcEnvFactory通过工厂模式创建的,一个RpcEnv包含名称、地址、端口、配置和可使用的核数等基本信息,还提供注册RpcEndpoint的方法,通过RpcEndpoint获取RpcEndpointRef引用的方法,还有关闭该RpcEnv的方法,包含一个文件下载服务器用来进行数据传输等等。

RpcEndpoint源码

 //用于创建[[RpcEnv]]的工厂类。它必须有一个空构造函数,这样才能使用反射创建它。
 //使用工厂模式
private[spark] trait RpcEnvFactory {
  //构造函数
  def create(config: RpcEnvConfig): RpcEnv
}

/**
 *RPC的一个端点,用于定义给定消息要触发的函数。
 *保证按顺序调用“onStart”、“receive”和“onStop”。
 *端点的生命周期是:
 * {@code constructor -> onStart -> receive* -> onStop}
 *注意:“receive”可以同时调用。如果您希望“receive”是线程安全的,请使用[[ThreadSafeRpcEndpoint]]
 *///如果从除“onError”之外的[[RpcEndpoint]]方法之一引发任何错误,则将调用“onError“并说明原因。
 //如果“onError”引发错误,[[RpcEnv]]将忽略它。
private[spark] trait RpcEndpoint {

   //此[[RpcEndpoint]]注册到的[[RpcEnv]]。
  val rpcEnv: RpcEnv

   //此[[RpcEndpoint]]的[[RpcEndpointRef]]`self将在调用“onStart”时生效。当调用“onStop”时,“self”将变为“null”。
   //注意:因为在“onStart”之前,[[RpcEndpoint]]尚未注册,并且没有有效的[[RpcEndpointRef]]。
   //因此,在调用“onStart'”之前不要调用“self”。
   //final 不能再派生 引用通常用来发消息 本质是该节点
  final def self: RpcEndpointRef = {
    //require--用于验证先决条件 不满足则抛出异常
    require(rpcEnv != null, "rpcEnv has not been initialized")
    rpcEnv.endpointRef(this)
  }

   //处理来自“RpcEndpointRef.send”或“RpcCallContext.respoy”的消息。
   //如果收到不匹配的消息,“SparkException”将被抛出并发送到“onError”。
   //收消息的并对消息进行处理 不需要回复
  def receive: PartialFunction[Any, Unit] = {
    case _ => throw new SparkException(self + " does not implement 'receive'")
  }

   //处理来自“RpcEndpointRef.ask”的消息。如果收到不匹配的消息,“SparkException”将被抛出并发送到“onError”。
   //需要回复的消息 
  def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
    case _ => context.sendFailure(new SparkException(self + " won't reply anything"))
  }

   //在处理消息期间引发任何异常时调用。
  def onError(cause: Throwable): Unit = {
    // By default, throw e and let RpcEnv handle it
    throw cause
  }

   //当“remoteAddress”连接到当前节点时调用。
  def onConnected(remoteAddress: RpcAddress): Unit = {
    // By default, do nothing.
  }

   //当“remoteAddress”丢失时调用。
  def onDisconnected(remoteAddress: RpcAddress): Unit = {
    // By default, do nothing.
  }

   //当当前节点和“remoteAddress”之间的连接中发生某种网络错误时调用。
  def onNetworkError(cause: Throwable, remoteAddress: RpcAddress): Unit = {
    // By default, do nothing.
  }

   //在[[RpcEndpoint]]开始处理任何消息之前调用。
  def onStart(): Unit = {
    // By default, do nothing.
  }

   //在[[RpcEndpoint]]停止时调用`self在这个方法中将为null,您不能使用它来发送或询问消息。
  def onStop(): Unit = {
    // By default, do nothing.
  }

   //一种方便的方法来停止[[RpcEndpoint]]。
  final def stop(): Unit = {
    val _self = self
    if (_self != null) {
      //通过rpcEnv来停止节点
      rpcEnv.stop(_self)
    }
  }
}

 //需要RpcEnv线程安全地向其发送消息的特性。
 //线程安全意味着在同一个[[ThreadSafeRpcEndpoint]]处理下一条消息之前先处理一条消息。
 //换言之,在处理下一条消息时,对[[ThreadSafeRpcEndpoint]]内部字段的更改是可见的,
 //并且[[ThreadSafeRpcEndpoint]]中的字段不必是可变的或等效的。
 //但是,不能保证同一个线程会对不同的消息执行相同的[[ThreadSafeRpcEndpoint]]。
private[spark] trait ThreadSafeRpcEndpoint extends RpcEndpoint

//使用专用线程池传递消息的端点。
private[spark] trait IsolatedRpcEndpoint extends RpcEndpoint {

   //用于传递消息的线程数。默认情况下,使用单个线程。
   //请注意,请求多个线程意味着端点应该能够同时处理来自多个线程的消息,以及所有需要处理的事情(包括无序传递到端点的消息)。
  def threadCount(): Int = 1

}

一个RpcEndpoint必定有一个注册的RpcEnv环境,和一个对应的引用RpcEndpointRef,receive用来处理单向消息,即不需要回复的消息,receiveAndReply负责处理需要回复的消息,需要返回给消息发送者回调,比如消息发送成功或者失败,当远程端点连接过来时针对不同的状态有不同的处理方式,onStart方法负责启动端点,onStop主要是停止端点。RpcEndpoint内部针对收到的消息有不同的处理方式,但是没有消息发送的方法,发消息的功能主要由引用RpcEndpointRef来实现。

RpcEndpointRef源码

//通常用来给远程节点发消息 通过引用来给节点发消息
private[spark] abstract class RpcEndpointRef(conf: SparkConf)
  extends Serializable with Logging {
  
  //最多重试次数
  private[this] val maxRetries = RpcUtils.numRetries(conf)

  //重试等待时间
  private[this] val retryWaitMs = RpcUtils.retryWaitMs(conf)

  //默认请求超时时间
  private[this] val defaultAskTimeout = RpcUtils.askRpcTimeout(conf)

  //返回引用的地址
  def address: RpcAddress

  //引用的名称
  def name: String

   //发送单向异步消息。即发即弃的意思。
  def send(message: Any): Unit

   //向相应的[[RpcEndpoint.receiveAndReply)]]发送消息,并返回[[AbortableRpcFuture]]以在指定的超时内接收回复。
   //[[AbortableRpcFuture]]实例使用附加的“abort”方法包装[[Future]]。
   //此方法只发送一次消息,从不重试。
   //Future,是一种用于指代某个尚未就绪的值的对象。而这个值,往往是某个计算过程的结果。
   //给远程endpoint节点发送消息后 等待回复 由AbortableRpcFuture负责接收回复消息
  def askAbortable[T: ClassTag](message: Any, timeout: RpcTimeout): AbortableRpcFuture[T] = {
    throw new UnsupportedOperationException()
  }

   //向相应的[[RpcEndpoint.receiveAndReply)]]发送消息,并返回[[Future]]以在指定的超时内接收回复。
   //此方法只发送一次消息,从不重试。
   //没有添加abort方法 也就是不丢弃消息 
  def ask[T: ClassTag](message: Any, timeout: RpcTimeout): Future[T]


   //向相应的[[RpcEndpoint.receiveAndReply)]]发送消息,并向返回[[Future]]在默认超时内接收回复。
   //此方法只发送一次消息,从不重试。
   //给定默认超时时间
  def ask[T: ClassTag](message: Any): Future[T] = ask(message, defaultAskTimeout)

   //向相应的[[RpcEndpoint.receiveAndReply]]发送消息,并在默认超时内获取结果,如果失败,则引发异常。
   //注意:这是一个阻塞操作,可能会花费大量时间,所以不要在[[RpcEndpoint]]的消息循环中调用它。
  def askSync[T: ClassTag](message: Any): T = askSync(message, defaultAskTimeout)

   //向相应的[[RpcEndpoint.receiveAndReply]]发送消息,并在指定的超时内获取结果,如果失败,则引发异常。
   //注意:这是一个阻塞操作,可能会花费大量时间,所以不要在[[RpcEndpoint]]的消息循环中调用它。
  def askSync[T: ClassTag](message: Any, timeout: RpcTimeout): T = {
    //获取返回结果
    val future = ask[T](message, timeout)
    timeout.awaitResult(future)
  }

}

//如果RPC中止,则引发异常。
private[spark] class RpcAbortException(message: String) extends Exception(message)

 //[[Future]]的包装器,但添加了中止方法。
 //这在长期运行的RPC中使用,并提供了一种中止RPC的方法。
 //在获取回复结果的时候 可以通过abort方法终止此次rpc 即放弃获取结果
private[spark]
class AbortableRpcFuture[T: ClassTag](val future: Future[T], onAbort: Throwable => Unit) {
  def abort(t: Throwable): Unit = onAbort(t)
}

一个RpcEndpointRef中主要是包含发送消息和发送请求的方法,send发送单向消息,发完消息就不管了,askAbortable发送一个消息并且等待回复,等待结果通过Future获取,并且如果发送消息等待时间过长可以终止,ask方法发送一次消息且等待回复,超过默认等待时间则放弃,发送消息过程中RPC终止则抛出异常。

总结:RpcEnv负责管理RpcEndpoint和RpcEndpointRef,RpcEndpoint是具体的通信端点,负责对收到的消息进行处理,RpcEndpointRef负责发送消息给绑定的RpcEndpoint,即如果一个RpcEndpoint想给远程的RpcEndpoint发送消息,首先在RpcEnv中找到该远程RpcEndpoint的引用RpcEndpointRef,然后通过该引用给该RpcEndpoint发消息。(ps:代码中有些英语比较简单直接复制通过百度翻译的,有不顺畅的见谅)