概要
Spark RPC之RpcRequest请求处理流程中介绍了spark处理RpcRequest请求的流程在client获取到server的RpcEndpointRef之后,如果是单向的请求,即调用RpcEndpointRef.send方法,则流程完毕,若有返回值,即调用RpcEndpointRef.ask方法,并等待server(接收消息的RpcEndpoint)返回消息。在server返回消息的时候,需要使用RpcCallContext来返回消息。RpcResponseCallback将值返回给client。
RPC 通信时,本质两端都是RpcEndpoint,这里说的server和client是基于通信的主客体来说的。即发送消息的是client,接收消息的是server。server的处理结果返回给client,主要由RpcCallContext、RpcResponseCallback完成,此外RpcResponseCallback在client端负责异步提取结果。查看其UML
1. 基础概念
Server的处理结果要想reply
给Client,需要借助RpcCallContext
、RpcResponseCallback
来实现。RpcResponseCallback在client端负责异步提取结果。UML如下:
1.1 基础概念之RpcCallContext
如上图,RpcCallContext最终有两个实现类
LocalNettyRpcCallContext
: 使用scala.concurrent.Promise
中的success()
方法处理client和server在一台机器的情况RemoteNettyRpcCallContext
:用于处理远程server的情况
这里主要介绍下RemoteNettyRpcCallContext:
1.1.1 RemoteNettyRpcCallContext的定义
从这里我们可以看到,RemoteNettyRpcCallContext维护了属性RpcResponseCallback,server端处理RpcRequest时调用其onSuccess方法将结果返回给client,client端则调用onSuccess时配合异步返回结果,查看RpcResponseCallback定义
1.2 基础概念之RpcResponseCallback
RpcResponseCallback
只定了两个抽象方法,分别处理成功和错误两种情况
1.2.1 在Client端使用RpcResponseCallback
查看RpcResponseCallback
的子类RpcOutboxMessage
:
RpcResponseCallback.onSuccess
接收外部传入的onSuccess
函数,该函数定义在NettyRpcEnv.ask
方法中,定义以及在创建RpcOutboxMessage(RpcResponseCallback的子类)
时对onSucces()的传入如下 :
(client, response) => onSuccess(deserialize[Any](client, response))
这个匿名函数,作为RpcOutboxMessage的第三个参数,去返回给了RpcOutboxMessage中的onSuccess()
函数。这里很奇妙。
1.2.2 在Server端使用RpcResponseCallback
在Server端的Receive函数里面,也是有使用匿名RpcResponseCallback类
的,并且要执行重要的回复Client端ResponseMessage的职责。
并且在后续返回消息过程中,会使用到这里onSuccess()函数来调用TransportRequestHandler的respond(RpcResponse)
来从Channel
返回Response给Client。这也就是为什么说server端处理RpcRequest时调用其onSuccess方法将结果返回给client,client端则调用onSuccess时配合异步返回结果
1.2.3 RpcResponseCallback总结
总体而言,在client.ask()与server.receive()中都会去使用RpcResponseCallback。不同之处在于:
client.ask()
:是通过RpcOutboxMessage继承RpcResponseCallback来,并重写onSuccess()方法传入RpcOutboxMessage来实现的server.receive()
: 是通过给receive函数传入一个匿名RpcResponseCallback内部类来实现的。这个匿名内部类会重写onSuccess()方法,等待Server端各种处理逻辑(注册、清除等)完成之后,才会在ReceiveAndReply()中去调用这个onSuccess(),让onSuccess()去给Client端发送Response,从而Client端调用onSuccess()接收到此Response。从而形成完整的闭环。
2. RpcResponse处理流程
这里,篇幅所限,无法展示完整的图,因此有需要可以下载完整的图Client与Server基于Rpc通信底层流程图
RpcResponse处理流程的分析位于下面的步骤3中,为了更好理解整个过程,我们从请求开始,完整分析
- client发送信息给server
- server处理收到信息,返回结果给client
- client处理server返回的信息
2.1 client发送带requestId的RpcRequest请求
spark使用了client连接池
实现netty多路复用
,如下:
因此一个client会发送不止一个请求,此时需要requestId跟踪请求,处理有返回值的情况
。Spark RPC之Dispatcher、Inbox、Outbox中介绍了client借助于Outbox发起远程请求的过程,如下
如上图,始于NettyRpcEndpointRef.ask
方法,终于Outbox.drainOutbox
方法,在drainOutbox方法中,while(true)中使用TransportClient
发送数据,如下
如上图注释处,我们只关注访问远程server的情况
,此时message类型为RpcOutboxMessage
,流程如下:
- 在
drainOutbox
方法中,RpcOutboxMessage调用sendWith方法,使用TransportClient发送信息。 - TransportClient.sendRpc方法中生成requestId,同时将(requestId, callback)信息添加到TransportResponseHandler的outstandingRpcs中。如下,后续client会根据这个requestId处理server返回的信息
- 调用
Netty Channel
的writeAndFlush
方法发送给远程server,信息内容为requestId + message
2.2 server处理RpcRequest,返回带requestId的RpcResponse
client通过channel发送请求给server,server端TransportChannelHandler
处理收到的请求,我们在Spark RPC之RpcRequest请求处理流程中有介绍,流程如下 :
如上图,流程终于Inbox.process
方法,接着分析process方法
Endpoint
处理完信息后,依次调用RemoteNettyRpcCallContext、RpcResponseCallback
方法返回信息,查看其实例化信息
上图可以看到RpcResponseCallback
通过匿名内部类的方式实例化,同时还看到server返回的RpcResponse
包含了请求的requestId
,对应的respond方法如下
至此,我们跟踪了server端处理RpcRequest(requestId, message),返回RpcResponse(requestId, message)的流程,至于具体的请求内容和处理结果,和具体的Endpoint实现有关,如Master和Worker,可以查看Spark RPC之Master实现,Spark RPC之Worker实现中receiveAndReply方法逻辑。
2.3 client处理server端返回的RpcResponse
和server端类似,创建TransportClient
对象时,将TransportChannelHandler
注册到底层的netty pipeline
中,因此处理server返回数据也是从TransportChannelHandler
开始,流程如下 :
TransportChannelHandler
接收server返回的信息,交给TransportResponseHandler处理handle()
方法中根据server返回的requestId从集合outstandingRpcs中获取callback对象,这个callback对象是步骤1.client发送带requestId的RpcRequest请求中,client给server发送请求前,调用addRpcRequest(requestId, callback)方法添加进去的。- 调用步骤2中获取到的
RpcResponseCallback对象的onSuccess()
将结果异步返回,此处的RpcResponseCallback对象是RpcOutboxMessage的实例,参考最开始关于RpcResponseCallback的介绍,而server端也有RpcResponseCallback实例化对象,是通过匿名内部类实现的,作用也不同,不要混淆。 - 步骤3中处理的结果,调用
onComplete
方法消费,例如Worker向Master注册的例子
3. 总结
完整的介绍了Spark RPC远程请求过程的底层流程:
client端
调用RpcEndpointRef.ask方法发送RpcRequest请求。server端
处理RpcRequest,并返回RpcResponse。client端
处理返回的RpcResponse。- 并介绍了上述过程如何使用
requestId跟踪请求
。此外,介绍了RpcCallContext、RpcResponseCallback如何在server端发挥作用,返回RpcResponse给client,以及RpcResponseCallback在client异步处理数据。