上一节介绍了消息发件箱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知识点做了注解,如果能全部仔细看完,一定会有所收获的,有理解错误的地方欢迎指正,到目前为止原理源码阅读完毕,后续会通过具体的例子来结合讲解。