DFSClient 会获取一个 ClientProtocolPB 协议的代理对象,并在这个代理对象上调用 RPC 方法,代理对象会调用 RPC.Client.call() 方法将序列化之后的 RPC 请求发送到服务器。

一、概述

rpc_client

二、请求程序

DFSClient,YarnClient,DFSAdmin 等等,对外暴露接口调用,内部调用下层接口实现功能。

DFSClient 是一个实现了分布式文件系统客户端功能的类,是用户使用 HDFS 各项功能的起点。DFSClient 会连接到 HDFS,对外提供管理文件/目录、读写文件以及管理与配置 HDFS 系统等功能。

DFSClient 会持有一个 ClientProtocol 对象向 Namenode 发送请求~

namenode 字段保存了一个实现了 ClientProtocol 接口的对象,DFSClient 通过 ProxyInfo 类来获取这个对象的引用,而这个 ProxyInfo 对象则是通过调用 NameNodeProxies.createProxy() 方法产生的。

三、Stub 程序

3.1. 协议层

不同的请求本质上是不同的协议。因此,有众多不同的协议实现类。在这些实现类中,主要负责对各自请求的结构体进行序列化与反序列化的操作。另外,每个协议实现类中,都会有一个代理(RPCProxy)的成员对象,请求通过调用代理的接口向下透传,最终向服务段发送请求并获取到响应内容。

获取客户端 proxy 做的第一件事情就是将 RPC 请求序列化,Hadoop 2.x 默认使用 protobuf 作为序列化工具,Hadoop RPC 框架也支持其他的序列化框架。Hadoop 定义了 RpcEngine 接口抽象使用不同序列化框架的 RPC 引擎。

rpcEngine

ClientNamenodeProtocolTranslatorPB 类持有一个实现了 ClientNamenodeProtocolPB 接口的对象,ClientNamenodeProtocolTranslatorPB 会将 ClientProtocol.rename(String, String)调用的参数序列化成一个 RenameRequestProto 对象,然后在 ClientNamenodeProtocolPB 对象上调用 rename(RenameRequestProto)方法。

3.2. 代理层

代理层也可以理解为是 RPC 引擎层,向上提供了通用的代理对象和处理类,向下调用通信层的接口向服务端发送请求。RPCEngine 本身是一个接口,具体实现类为 ProtobufRpcEngine 和 WritableRpcEngine,两个引擎中各自都包含了一个 invoker 类,实现了 java 动态代理框架中处理类的接口,在 invoker 内部,则封装了对 RPC.Client 的操作。

对于采用了高可用的模块而言,例如 Namenode, Resourcemanager, 对应的客户端其整体结构, 会在协议层与 RPC 引擎层之间再多加一层抽象, 称为重试代理(RetryProxy)。

在该层,会进行请求失败的重试,因为向 Standby 的节点发送请求,或者 active 的节点异常情况,均无法得到响应,因此需要进行重试。

同样,向上仍旧是通过java动态代理的方式返回代理对象,向下则调用对应引擎层的接口,获取代理对象,并调用对应的方法完成请求。

这样对于上层的客户端,则不需要感知下面的重试逻辑,完全是透明的处理逻辑。

3.3. 路由层

Hadoop RPC 巧妙地使用了 Java 动态代理机制,ClientNamenodeProtocolTranslatorPB 持有的 ClientNamenodeProtocolPB 对象其实是通过 Java动态代理机制获取的一个 ClientNamenodeProtocolPB 接口的代理对象,调用 RPC.getProtocolProxy() 方法获取,这个代理对象内部封装了一个 ProtobufRpcEngine.Invoker对象。对 ClientNamenodeProtocolPB 接口的调用都会由这个 Invoker 对象的 invoke()方法代理。

invoke2

Invoker.invoke() 方法会首先构造一个描述 RPC 调用信息的对象 RequestHeaderProto,记录了客户端在什么协议上调用了什么方法(在 ClientProtocol 协议上调用了 rename 方法)

1
RequestHeaderProto rpcRequestHeader = constructRpcRequestHeader(method);

然后将 RequestHeaderProto 对象以及调用参数对象 RenameRequestProto 包装成一个 RpcRequestWrapper 对象,调用底层 RPC.Client 类提供的 call()方法将 rename 请求发送到远程服务器了~

1
2
3
val = (RpcResponseWrapper) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
new RpcRequestWrapper(rpcRequestHeader, theRequest), remoteId,
fallbackToSimpleAuth);

四、传输层 Client

RPC 的网络通信, 具体包括 RPC 连接(Hadoop 中均采用 tcp 的方式) 的建立,请求的发送与响应的接收。RPC.Client 内部维护了一个连接(Connection) 列表,每个连接都是一个独立的线程,在线程内部负责正常请求的发送与响应的接收。

代理类 proxy 会调用 Client.call() 方法将 RPC 请求发送到远程服务器,然后等待远程服务器的响应。如果远程服务器响应请求时出现异常,则在 call() 方法中抛出异常。

五、总结