1、前言
昨天分析了ipc包下的RPC、Client类,今天来分析下ipc.Server。Server类因为是Hadoop自己使用,所以代码结构以及流程都很清晰,可以清楚的看到实例化、停止、运行等过程。
2、Server类结构
上面是Server的五个内部类,分别介绍一下:
1)Call
用以存储客户端发来的请求,这个请求会放入一个BlockQueue中;
2)Listener
监听类,用以监听客户端发来的请求。同时Listener下面还有一个静态类,Listener.Reader,当监听器监听到用户请求,便用让Reader读取用户请求。
3)Responder
响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
4)Connection
连接类,真正的客户端请求读取逻辑在这个类中。
5)Handler
请求(blockQueueCall)处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。
3、Server初始化
第一篇博客说了,Server的初始化入口在RPC.getServer中,getServer其实是调用的RPC.Server静态类中的构造方法,我们看看Namenode创建RPCServer的方法和RPC.Server构造方法代码:
-
private void initialize(Configuration
conf) throws IOException {
-
…
-
this.serviceRpcServer = RPC.getServer(this,
dnSocketAddr.getHostName(),
-
dnSocketAddr.getPort(), serviceHandlerCount,
-
false, conf, namesystem.getDelegationTokenSecretManager());
-
this.serviceRpcServer.start();
-
}
|
-
public Server(Object instance, Configuration conf, String bindAddress, int port,
-
int numHandlers, boolean verbose,
-
SecretManager<? extends TokenIdentifier>
secretManager)
-
throws IOException {
-
super(bindAddress, port, Invocation.class,
numHandlers, conf,
-
classNameBase(instance.getClass().getName()), secretManager);
-
this.instance = instance;
-
this.verbose = verbose;
-
}
|
该方法调用了父类的构造方法,如下:
-
protected Server(String bindAddress, int port,
-
Class<? extends Writable> paramClass, inthandlerCount,
-
Configuration conf, String serverName, SecretManager<? extends TokenIdentifier>
secretManager)
-
throws IOException {
-
this.bindAddress = bindAddress;
-
this.conf = conf;
-
this.port = port;
-
this.paramClass = paramClass;
-
this.handlerCount = handlerCount;
-
this.socketSendBufferSize = 0;
-
this.maxQueueSize = handlerCount * conf.getInt(
-
IPC_SERVER_HANDLER_QUEUE_SIZE_KEY,
-
IPC_SERVER_HANDLER_QUEUE_SIZE_DEFAULT);
-
this.maxRespSize = conf.getInt(IPC_SERVER_RPC_MAX_RESPONSE_SIZE_KEY,
-
IPC_SERVER_RPC_MAX_RESPONSE_SIZE_DEFAULT);
-
this.readThreads = conf.getInt(
-
IPC_SERVER_RPC_READ_THREADS_KEY,
-
IPC_SERVER_RPC_READ_THREADS_DEFAULT);
-
this.callQueue = new LinkedBlockingQueue<Call>(maxQueueSize);
-
this.maxIdleTime =2*conf.getInt("ipc.client.connection.maxidletime", 1000);
-
this.maxConnectionsToNuke = conf.getInt("ipc.client.kill.max", 10);
-
this.thresholdIdleConnections = conf.getInt("ipc.client.idlethreshold", 4000);
-
this.secretManager = (SecretManager<TokenIdentifier>)
secretManager;
-
this.authorize =
-
conf.getBoolean(HADOOP_SECURITY_AUTHORIZATION, false);
-
this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled();
-
-
-
listener = new Listener();
-
this.port = listener.getAddress().getPort();
-
this.rpcMetrics = RpcInstrumentation.create(serverName,this.port);
-
this.tcpNoDelay = conf.getBoolean("ipc.server.tcpnodelay",false);
-
-
-
responder = new Responder();
-
-
if (isSecurityEnabled) {
-
SaslRpcServer.init(conf);
-
}
-
}
|
不难看出,父类的构造方法就初始化了一些配置和变量。
4、Server运行
在上面第一段代码中,还有一句RpcServer.start()的方法,在调用构造函数初始化一些变量之后,Server就可以正式运行起来了:
-
public synchronized void start()
{
-
responder.start();
-
listener.start();
-
handlers = new Handler[handlerCount];
-
-
for (int i
= 0; i < handlerCount; i++) {
-
handlers[i] = new Handler(i);
-
handlers[i].start();
-
}
-
}
|
responder、listener、handlers三个对象的线程均阻塞了,前两个阻塞在selector.select()方法上,handler阻塞在callQueue.take()方法,都在等待客户端请求。Responder设置了超时时间,为15分钟。而listener还开启了Reader线程,该线程也阻塞了。
4、Server接受请求流程
1)监听到请求
Listener监听到请求,获得所有请求的SelectionKey,执行doAccept(key)方法,该方法将所有的连接对象放入list中,并将connection对象与key绑定,以供reader使用。初始化玩所有的conne对象之后,就可以激活Reader线程了。
-
void doAccept(SelectionKey key) throws IOException,
OutOfMemoryError {
-
Connection c = null;
-
ServerSocketChannel server = (ServerSocketChannel) key.channel();
-
SocketChannel channel;
-
while ((channel = server.accept()) != null)
{
-
channel.configureBlocking(false);
-
channel.socket().setTcpNoDelay(tcpNoDelay);
-
Reader reader = getReader();
-
try {
-
reader.startAdd();
-
SelectionKey readKey = reader.registerChannel(channel);
-
c = new Connection(readKey, channel, System.currentTimeMillis());
-
readKey.attach(c);
-
synchronized (connectionList) {
-
connectionList.add(numConnections, c);
-
numConnections++;
-
}
-
…
-
} finally {
-
reader.finishAdd();
-
}
-
}
-
}
|
2)接收请求
Reader的run方法和Listener基本一致,也是获得所有的SelectionKey,再执行doRead(key)方法。该方法获得key中绑定的connection,并执行conection的readAndProcess()方法:
-
void doRead(SelectionKey key) throws InterruptedException
{
-
int count = 0;
-
Connection c = (Connection)key.attachment();
-
if (c == null)
{
-
return;
-
}
-
c.setLastContact(System.currentTimeMillis());
-
-
try {
-
count = c.readAndProcess();
-
} catch (InterruptedException ieo) {
-
…
-
}
-
if (count < 0)
{
-
…
-
closeConnection(c);
-
c = null;
-
}
-
else {
-
c.setLastContact(System.currentTimeMillis());
-
}
-
}
|
-
public int readAndProcess() throws IOException,
InterruptedException {
-
-
-
while (true)
{
-
int count = -1;
-
if (dataLengthBuffer.remaining() > 0)
{
-
count = channelRead(channel, dataLengthBuffer);
-
…
-
if (!rpcHeaderRead) {
-
-
if (rpcHeaderBuffer == null)
{
-
rpcHeaderBuffer = ByteBuffer.allocate(2);
-
}
-
count = channelRead(channel, rpcHeaderBuffer);
-
if (count < 0 ||
rpcHeaderBuffer.remaining() > 0) {
-
return count;
-
}
-
-
int version = rpcHeaderBuffer.get(0);
-
byte[] method = new byte[]
{rpcHeaderBuffer.get(1)};
-
authMethod = AuthMethod.read(new DataInputStream(
-
new ByteArrayInputStream(method)));
-
dataLengthBuffer.flip();
-
…
-
dataLengthBuffer.clear();
-
…
-
-
rpcHeaderBuffer = null;
-
rpcHeaderRead = true;
-
continue;
-
}
-
…
-
data = ByteBuffer.allocate(dataLength);
-
}
-
-
-
count = channelRead(channel, data);
-
-
if (data.remaining() == 0)
{
-
…
-
if (useSasl) {
-
saslReadAndProcess(data.array());
-
} else {
-
-
processOneRpc(data.array());
-
}
-
…
-
}
-
return count;
-
}
-
}
|
3)获得call请求
在Connection中解析param请求中,解析了请求数据,并构造Call对象,将其加入callQueue。
-
private void processData(byte[]
buf) throws IOException, InterruptedException {
-
DataInputStream dis =
-
new DataInputStream(new ByteArrayInputStream(buf));
-
int id = dis.readInt();
-
…
-
-
Writable param = ReflectionUtils.newInstance(paramClass, conf);
-
param.readFields(dis);
-
-
Call call = new Call(id, param, this);
-
callQueue.put(call);
-
incRpcCount();
-
}
|
4)处理call对象
Connection给callQueue添加了call对象,阻塞的Handler可以继续运行了,拿出一个call对象,并调用RPC.Call方法
-
-
while (running) {
-
final Call call = callQueue.take();
-
CurCall.set(call);
-
value = call(call.connection.protocol, call.param,
-
call.timestamp);
-
CurCall.set(null);
-
-
synchronized (call.connection.responseQueue) {
-
setupResponse(buf, call,
-
(error == null) ? Status.SUCCESS
: Status.ERROR,
-
value, errorClass, error);
-
…
-
responder.doRespond(call);
-
}
-
}
|
5)响应请求
上面代码中的setupResponse将call的id和状态发送回去,再设置了call中的response:ByteBuffer,之后就开始responder.doRespond(call)了,processResponse以及Responder.run()没太弄明白,就先不说了。
-
void doRespond(Call call) throws IOException
{
-
synchronized (call.connection.responseQueue)
{
-
-
call.connection.responseQueue.addLast(call);
-
if (call.connection.responseQueue.size()
== 1) {
-
-
processResponse(call.connection.responseQueue,true);
-
}
-
}
-
}
|
6、总结
Server用的标准的Java TCP/IP NIO通信,同时请求的超时使用基于BlockingQueue以及wait/notify机制实现。使用的模式是reactor模式,关于nio和reactor可以参考这个博客。
对于服务器端接收多个连接请求的需求,Server采用Listener来监听连接的事件,并用Listener.Reader来监听网络流读以及Responder监听写的事件,当有实际的网络流读写时间发生之后,解析了请求Call之后,添加进阻塞队列,并交由多个Handlers来处理请求。
这个方法比TCP/IP BIO好处就是可接受很多的连接,而这些连接只在真实的请求时才会创建线程处理,称之为一请求一处理。但是,连接上的请求发送非常频繁时,TCP/IP NIO的方法并不会带来太大的优势。
但是Hadoop实际场景中,通常是服务器端支持大量的连接数(Namenode连上几千个Datanode),但是连接发送的请求并不会太多(heartbeat、blockreport都有较长间隔)。这样就造成了Hadoop不适合实时的、多请求的运算,带来的代价是模型、实现简单,但是这也为以后的扩展埋下了祸根。
P.S.: 以上分析基于稳定版0.20.203.0rc1。