上一节介绍了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:代码中有些英语比较简单直接复制通过百度翻译的,有不顺畅的见谅)