前言
在一个RPC系统中一定包含两个角色 Server/Client。本文重点介绍Hadoop系统中的RPC实现中的 RPC Client端实现。Hadoop绝大部分代码由Java实现,下面的Java类实现了RPC client的主要逻辑:
org
总览
在当前Hadoop的RPC Client架构中有三个重要的角色,RPCClient, Connection, Call
RPC Client: Hadoop应用在初始化RPC Client的时候需要初始化一些必要的配置参数例如链接超时时间(ConnectionTimeout)等,RPC Client 并不需要对网络链接进行初始化。
Connection: 每个RPC client 可以有多个Connection, 每个Connection代表特定的服务端TCP 链接,以及一些必要的配置参数。
Call: 在Connection建立的情况下,客户端可以利用这个已经建立的TPC连接反复发送RPC call, 每个RPC call代表一次从客户端到服务端的远程调用。
以上三者关系如下图所示,本文将结合部分源码在接下来的部分阐述RPC client的工作原理
建立Connection
在Client初始化完成以后,Client对象实质上无法得知其将要链接的服务端网络地址,因此在第一个RPC请求到达之前没有任何网络链接的产生。每一个RPC请求会指定服务端的网络地址,在请求到达以后Client对象首先检查是否已经存在这样的一个网络链接,如果存在的话则用之进行网络交互,否则新建一个。代码片段如下:
connection
RPC Call的发送
RPC Client发送RPC request的主要入口函数是:
public
在RPC Call到达以后,Connection选择立即发送该RPC Call, 并且暂时保存该RPC Call对象(此时Connection对象并没有立刻要求网络响应)。值得注意的是 Connection使用一个线程池来发送RPC Call。这个线程池的初始化代码如下:
synchronized
这样初始化的线程池特点如下:
- 空闲时不会有线程驻留
- 线程的个数上线是Integer.MAX_VALUE
- 对于任何一个线程空闲时间超过60秒以后,会被回收。
public
Rpc Call可以是同步的方式也可以是异步的方式。如果是异步方式,则构造一个Future,交给调用段去管理改Future的生命周期,该Future实质上是封装了同步获取RPC响应的函数。如果是同步调用,则直接调用该函数(阻塞当前线程,直到RPC响应)
if
RPC Response 捕获
由于网络响应是不可预期的,因此函数getRpcResponse不能期望网络立即能得到网络的响应,hadoop采用了wait/notify的方式来实现基于通知机制的RPC响应。简单来说,函数getRpcResponse先行调用 call对象的wait()方法阻塞当前线程(假设为t),等到有网络响应是该call对象的notify方法会被调用,唤起线程t。参考函数:
org
事实上, Connection继承自Thread类,其run方法的实现实质上是持续的从网络上读取数据。根据数据的返回内容解析出响应的callId,并调用与之对应的call对象的notify方法。参考函数:
org.apache.hadoop.ipc.Client$Connection::run
网络输入流-InputStream
当一个网络请求发送给Server端以后,client客户端的行为由两个参数控制:RpcTimeout和PingInterval,行为如下
- 尝试从网络读出数据,如果读到至少一个字节则开始解析网络响应,如果得到一个SocketTimeoutException则转2。值得注意的是Client的超时时间是由PingInterval决定的,所以一旦客户端收到这个异常,则可断言网络上至少已经等待PingInterval的时长。
- 检查等待时间是不是超过RpcTimeout,如果超过,则把SocketTimeoutException抛给调用端;如果没有,则发送一个Ping包到Server端.
注: PingInterval由参数:ipc.ping.interval指定;RpcTimeout由ipc.client.rpc-timeout.ms指定。需要指出的是:如果RpcTimeout设置为0,如果Server端迟迟不响应,那么client端将会无休止的sendPing()直到网络响应。
Hadoop靠一个自己封装的PingInputStream实现这个效果。
public
结论
一个RPC Client可以管理到多个RPC server的TCP链接,在TCP建立起来以后多个Call可以复用链接。RPC的响应采用异步通知的方式。封装的PingInputStream实现了在超时时间内周期性发送ping包的功能。