TransportClient类说明
先来看,官方文档给出的说明:
Client for fetching consecutive chunks of a pre-negotiated stream. This API is intended to allow efficient transfer of a large amount of data, broken up into chunks with size ranging from hundreds of KB to a few MB.
Note that while this client deals with the fetching of chunks from a stream (i.e., data plane), the actual setup of the streams is done outside the scope of the transport layer. The convenience method "sendRPC" is provided to enable control plane communication between the client and server to perform this setup.
For example, a typical workflow might be:
client.sendRPC(new OpenFile("/foo")) --> returns StreamId = 100
client.fetchChunk(streamId = 100, chunkIndex = 0, callback)
client.fetchChunk(streamId = 100, chunkIndex = 1, callback)
...
client.sendRPC(new CloseStream(100))
Construct an instance of TransportClient using TransportClientFactory. A single TransportClient may be used for multiple streams, but any given stream must be restricted to a single client, in order to avoid out-of-order responses.
NB: This class is used to make requests to the server, while TransportResponseHandler is responsible for handling responses from the server. Concurrency: thread safe and can be called from multiple threads.
用于获取预先协商的流的连续块的客户端。此API允许有效传输大量数据,分解为大小从几百KB到几MB的chunk。
注意,虽然该客户端处理从流(即,数据平面)获取chunk,但是流的实际设置在传输层的范围之外完成。提供便利方法“sendRPC”以使客户端和服务器之间的控制平面通信能够执行该设置。
例如,典型的工作流程可能是:
// 打开远程文件
client.sendRPC(new OpenFile(“/ foo”)) - >返回StreamId = 100
// 打开获取远程文件chunk-0
client.fetchChunk(streamId = 100,chunkIndex = 0,callback)
// 打开获取远程文件chunk-1
client.fetchChunk(streamId = 100,chunkIndex = 1,callback)
.. .
// 关闭远程文件
client.sendRPC(new CloseStream(100))
使用TransportClientFactory构造TransportClient的实例。
单个TransportClient可以用于多个流,但是任何给定的流必须限制在单个客户端,以避免无序响应。
注意:此类用于向服务器发出请求,而TransportResponseHandler负责处理来自服务器的响应。并发:线程安全,可以从多个线程调用。
简言之,可以认为TransportClient就是Spark Rpc 最底层的基础客户端类。主要用于向server端发送rpc 请求和从server 端获取流的chunk块。
下面看一下类的结构:
它有两个内部类:RpcChannelListener和StdChannelListener,这两个类的继承关系如下:
其公共父类GenericFutureListener 官方说明如下:
Listens to the result of a Future. The result of the asynchronous operation is notified once this listener is added by calling Future.addListener(GenericFutureListener).
即,监听一个Future 对象的执行结果,通过Future.addListener(GenericFutureListener)的方法,添加监听器来监听这个异步任务的最终结果。当异步任务执行成功之后,会调用监听器的 operationComplete 方法。在StdChannelListener 中,其operationComplete 方法其实就是添加了日志打印运行轨迹的作用,添加了异常的处理方法 handleFailure,它是一个空实现,如下:
其子类RpcChannelListener的handleFailure实现如下:
这个handleFailure 方法充当着失败处理转发的作用。其调用了 RpcResponseCallback (通过构造方法传入)的 onFailure 方法。
再来看一下TransportClient 的主要方法解释:
1. fetchChunk : Requests a single chunk from the remote side, from the pre-negotiated streamId. Chunk indices go from 0 onwards. It is valid to request the same chunk multiple times, though some streams may not support this. Multiple fetchChunk requests may be outstanding simultaneously, and the chunks are guaranteed to be returned in the same order that they were requested, assuming only a single TransportClient is used to fetch the chunks.其源码如下:
2. stream:Request to stream the data with the given stream ID from the remote end.其源码如下:
3. sendRpc:Sends an opaque message to the RpcHandler on the server-side. The callback will be invoked with the server's response or upon any failure.
4. uploadStream:Send data to the remote end as a stream. This differs from stream() in that this is a request to *send* data to the remote end, not to receive it from the remote.
5. sendRpcSync:Synchronously sends an opaque message to the RpcHandler on the server-side, waiting for up to a specified timeout for a response.
6. send:Sends an opaque message to the RpcHandler on the server-side. No reply is expected for the message, and no delivery guarantees are made.
7. removeRpcRequest:Removes any state associated with the given RPC.主要是从handler 中把监听的rpcRequest移除。
8. close:close the channel
9. timeOut: Mark this channel as having timed out.
可以看出,其主要是一个比较底层的客户端,主要用于发送底层数据的request,主要是数据层面的流中的chunk请求或者是控制层面的rpc请求,发送数据请求的方法中都有一个回调方法,回调方法是用于处理请求返回的结果。
TransportClient初始化
它是由TransportClientFactory 创建的。看TransportClientFactory 的核心方法: createClient(java.net.InetSocketAddress)的关键代码如下:
1 // 1. 添加一个 ChannelInitializer 的 handler
2 bootstrap.handler(new ChannelInitializer<SocketChannel>() {
3 @Override
4 public void initChannel(SocketChannel ch) {
5 TransportChannelHandler clientHandler = context.initializePipeline(ch);
6 clientRef.set(clientHandler.getClient());
7 channelRef.set(ch);
8 }
9 });
10
11 // Connect to the remote server
12 long preConnect = System.nanoTime();
13 // 2. 连接到远程的服务端,返回一个ChannelFuture 对象,调用其 await 方法等待其结果返回。
14 ChannelFuture cf = bootstrap.connect(address);
15 // 3. 等待channelFuture 对象其结果返回。
16 if (!cf.await(conf.connectionTimeoutMs())) {
17 throw new IOException(
18 String.format("Connecting to %s timed out (%s ms)", address, conf.connectionTimeoutMs()));
19 } else if (cf.cause() != null) {
20 throw new IOException(String.format("Failed to connect to %s", address), cf.cause());
21 }
在connect 方法中,初始化了handler。handler 被添加到ChannelPipiline之后,使用线程池来处理初始化操作,其调用了 DefaultChannelPipeline的callHandlerAdded0 方法,callHandlerAdded0调用了handler 的 handlerAdded 方法,handlerAdded内部调用了 initChannel 私有方法,initChannel又调用了保护抽象方法 initChannel,其会调用 ChannelInitializer自定义匿名子类的initChannel 方法。在这个 initChannel 方法中调用了TransportContext 的initializePipeline方法,在这个方法中实例化了 TransportClient对象。
我们再来看一下TransportContext 的initializePipeline方法的核心方法createChannelHandler:
再来看 NettyRpcEnv 是如何初始化transportContext 的:
从上面可以看到 rpcHandler 是NettyRpcHandler, 其依赖三个对象,Dispatcher 对象,nettyEnv 对象以及StreamManager 对象。
Dispatcher 对象已经有做说明,可以看我的博客spark 源码分析之六 -- Spark内置RPC机制剖析之二Dispatcher和Inbox剖析做进一步了解。
NettyEnv 对象就是NettyRpcEnv 对象。
NettyRpcHandler已经有做说明,可以看我的博客spark 源码分析之九 -- Spark内置RPC机制剖析之五StreamManager和NettyRpcHandler做进一步了解。
即channelRpcHandler 就是NettyRpcHandler实例。
关于TransportResponseHandler、TransportRequestHandler和TransportChannelHandler三个类的说明,可以参照博客spark 源码分析之十 -- Spark内置RPC机制剖析之六TransportResponseHandler、TransportRequestHandler和TransportChannelHandler剖析 做进一步了解。
TransportServer
官方说明:
Server for the efficient, low-level streaming service.
即:用于高效,低级别流媒体服务的服务器。
使用TransportContext createServer方法创建:
其构造方法源码如下:
重点看其init方法:
ServerBootstrap是用于初始化Server的。跟TransportClientFactory创建TransportClient类似,也有ChannelInitializer的回调,跟Bootstrap类似。参照上面的剖析。
至此,TransClient和TransServer的剖析完毕。