Hadoop RPC分析 (二) -- Server
来源:互联网 发布:彻底清除软件安装痕迹 编辑:程序博客网 时间:2024/05/17 22:27
[Hadoop RPC服务端:Server]
服务端的基本思路可以简述如下:通过网络获取到远程调用相关的信息,找到对应的方法,执行完成后,将结果通过网络发送给客户端。
对于服务端,我们将主要关心内部的实现机制,找到服务端处理一次远程调用的整个流程。
与Server相关的类主要为下面几个(都是Server的内部类)
Server.Listener & Server.Listener.Reader
将Listener和Reader放在一起,主要是因为Listener和Reader在一起合作,使用Java NIO来实现的网络请求的监听和接收。简单说来,就是Listener线程负责监听来自客户端的socket连接请求,做accept请求操作。然后将建立连接的channel注册到Reader上,由Reader来负责处理数据到达的事件。这里主要使用基于Java NIO实现的网络事件的处理,因此需要对Java NIO有基本的了解,才能读懂这里的代码逻辑。
Server.Connection
代表一个客户端和服务端的连接的访问通道。每个连接到服务端的channel都有一个附加Connection对象,来作为业务上的连接。服务端每次在accept客户端的连接请求的时候,会创建一个Connection对象,将之绑定在这个channel上。
在Connection中会读取数据,将数据反序列化成对应的对象。
Server.Call
表示一次远程调用业务,包含了本次远程过程调用的一些请求参数信息,以及执行完过程调用之后的返回结果信息。
Server.Responder
Responder在server模型中,主要负责远程调用结果的返回,将远程调用的结果通过socket发送给对应的客户端。
Server.Handler
Handler主要负责任务调度。从Server的Call队列中消费远程调用业务,交给Responder返回给客户端。
Server模型比客户端模型负责,借用一张来自(http://langyu.iteye.com/blog/1183337)的图来说明Server这边内部各个组成模块之间的关系
1、Server的入口
Server的入口可以认为是两部分,一个是构造函数生成一个Server对象,一个是start方法启动Server服务
protectedServer(String bindAddress,intport,
Class<?extendsWritable> rpcRequestClass,inthandlerCount,
intnumReaders,intqueueSizePerHandler, Configuration conf,
String serverName, SecretManager<?extendsTokenIdentifier> secretManager,
String portRangeConfig)
throwsIOException {
this.bindAddress= bindAddress;
this.conf= conf;
this.portRangeConfig= portRangeConfig;
this.port= port;
this.rpcRequestClass= rpcRequestClass;
this.handlerCount= handlerCount;
this.socketSendBufferSize= 0;
this.maxDataLength= conf.getInt(CommonConfigurationKeys.IPC_MAXIMUM_DATA_LENGTH,
CommonConfigurationKeys.IPC_MAXIMUM_DATA_LENGTH_DEFAULT);
if(queueSizePerHandler != -1) {
this.maxQueueSize= queueSizePerHandler;
}else{
this.maxQueueSize= handlerCount * conf.getInt(
CommonConfigurationKeys.IPC_SERVER_HANDLER_QUEUE_SIZE_KEY,
CommonConfigurationKeys.IPC_SERVER_HANDLER_QUEUE_SIZE_DEFAULT);
}
this.maxRespSize= conf.getInt(
CommonConfigurationKeys.IPC_SERVER_RPC_MAX_RESPONSE_SIZE_KEY,
CommonConfigurationKeys.IPC_SERVER_RPC_MAX_RESPONSE_SIZE_DEFAULT);
if(numReaders != -1) {
this.readThreads= numReaders;
}else{
this.readThreads= conf.getInt(
CommonConfigurationKeys.IPC_SERVER_RPC_READ_THREADS_KEY,
CommonConfigurationKeys.IPC_SERVER_RPC_READ_THREADS_DEFAULT);
}
this.callQueue =newLinkedBlockingQueue<Call>(maxQueueSize);
this.maxIdleTime= 2 * conf.getInt(
CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_KEY,
CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_DEFAULT);
this.maxConnectionsToNuke= conf.getInt(
CommonConfigurationKeysPublic.IPC_CLIENT_KILL_MAX_KEY,
CommonConfigurationKeysPublic.IPC_CLIENT_KILL_MAX_DEFAULT);
this.thresholdIdleConnections= conf.getInt(
CommonConfigurationKeysPublic.IPC_CLIENT_IDLETHRESHOLD_KEY,
CommonConfigurationKeysPublic.IPC_CLIENT_IDLETHRESHOLD_DEFAULT);
this.secretManager= (SecretManager<TokenIdentifier>) secretManager;
this.authorize=
conf.getBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION,
false);
// configure supported authentications
this.enabledAuthMethods= getAuthMethods(secretManager, conf);
this.negotiateResponse= buildNegotiateResponse(enabledAuthMethods);
// Start the listener here and let it bind to the port
listener=newListener();
this.port=listener.getAddress().getPort();
this.rpcMetrics= RpcMetrics.create(this);
this.rpcDetailedMetrics= RpcDetailedMetrics.create(this.port);
this.tcpNoDelay= conf.getBoolean(
CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_KEY,
CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_DEFAULT);
// Create the responder here
responder=newResponder();
if(secretManager !=null) {
SaslRpcServer.init(conf);
}
this.exceptionsHandler.addTerseExceptions(StandbyException.class);
}
初始化了Server的内部Call队列,这个队列将会由Handler线程来消费。初始化了Listener对象,初始化了Responder对象。
下面是启动Server的方法,启动Server的内部各个功能模块。
publicsynchronizedvoidstart() {
responder.start();
listener.start();
handlers=newHandler[handlerCount];
for(inti = 0; i <handlerCount; i++) {
handlers[i] =newHandler(i);
handlers[i].start();
}
}
根据数据的流向,从Listener开始来做流程跟进。Listener和Reader合作,来完成网络连接的建立。
2、Listener
Listener的属性如下。
privateServerSocketChannelacceptChannel=null;//the accept channel
privateSelectorselector=null;//the selector that we use for the server
privateReader[]readers=null;
privateintcurrentReader= 0;
privateInetSocketAddressaddress;//the address we bind at
privateRandomrand=newRandom();
privatelonglastCleanupRunTime= 0;//the last time when a cleanup connec-
//-tion (for idle connections) ran
privatelongcleanupInterval= 10000;//the minimum interval between
//two cleanup runs
privateintbacklogLength=conf.getInt(
CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_KEY,
CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_DEFAULT);
构造Listener对象时的操作,在Listener的构造函数中有体现
publicListener()throwsIOException {
address=newInetSocketAddress(bindAddress,port);
// Create a new server socket and set to non blocking mode
acceptChannel= ServerSocketChannel.open();
acceptChannel.configureBlocking(false);
// Bind the server socket to the local host and port
bind(acceptChannel.socket(),address,backlogLength,conf,portRangeConfig);
port=acceptChannel.socket().getLocalPort();//Could be an ephemeral port
// create a selector;
selector= Selector.open();
readers=newReader[readThreads];
for(inti = 0; i <readThreads; i++) {
Reader reader =newReader(
"Socket Reader #"+ (i + 1) +" for port "+port);
readers[i] = reader;
reader.start();
}
// Register accepts on the server socket with the selector.
acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
this.setName("IPC Server listener on "+port);
this.setDaemon(true);
}
打开一个服务端的channel,绑定服务端口,为服务端channel在Selector对象上注册接受事件。同时,启动了多个Reader线程。在Listener的构造方法中完成了对建立连接端口在selector的监听,这样当有连接请求到达时,就能够做相应的处理。
在Listener的run方法中,会完成处理建立连接请求的事件。以及将channel注册到reader上(在listener上只注册了建立连接的动作,没有注册数据读取动作)。
Listener线程启动之后的主体run方法,主要是进行selector的select操作来获取建立连接的事件,然后调用doAccept方法来建立连接。来看doAccept方法的实现,代码如下
voiddoAccept(SelectionKey key)throwsIOException, 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 = newConnection(readKey, channel, Time.now());
readKey.attach(c);
synchronized(connectionList) {
connectionList.add(numConnections, c);
numConnections++;
}
if(LOG.isDebugEnabled())
LOG.debug("Server connection from " + c.toString() +
"; # active connections: "+numConnections+
"; # queued calls: "+callQueue.size());
}finally{
reader.finishAdd();
}
}
}
通过代码中底色标注的部分来看,接收客户端的建立连接的请求,然后将这个建立连接的channel注册到一个Reader线程上。注意这里,在注册时,为每个channel附件了一个Connection对象,这个对象在后面部分会用到。
到这里,客户端和服务端的网络连接已经建立。接着,就是客户端数据请求到达服务端时的处理逻辑,来跟进Reader的代码。Reader线程在Listener构造函数中已经启动,这里直接来看Reader的主体run方法(源码略),只做了调用doRunLoop方法一件事情。跟进doRunLoop方法,其实现也很简单,做一个selector的select操作,然后调用doRead做数据读取。而doRead方法也只是取出和这个channel对应的Connection对象,在这个Connection上对象上调用readAndProcess方法进行处理。
Connection并不是一个线程,它只是表示客户端和服务端连接的业务通道,直接跟进它的readAndProcess方法,这个方法较长,删除了其中一些异常处理分支之后的代码如下
publicintreadAndProcess()
throwsWrappedRpcServerException, IOException, InterruptedException {
while(true) {
count = channelRead(channel,dataLengthBuffer);
if(!connectionHeaderRead) {
count = channelRead(channel,connectionHeaderBuf);
}
count = channelRead(channel,data);
processOneRpc(data.array());
returncount;
}
基本上的逻辑就很简单了,从这个Connection对象对应的channel上读数据,然后进行处理。在processOneRpc方法中,做了数据的处理。来跟进这个方法
部分源代码如下
privatevoidprocessOneRpc(byte[] buf)
throwsIOException, WrappedRpcServerException, InterruptedException {
finalDataInputStream dis =
newDataInputStream(newByteArrayInputStream(buf));
finalRpcRequestHeaderProto header =
decodeProtobufFromStream(RpcRequestHeaderProto.newBuilder(), dis);
processRpcRequest(header, dis);
}
去除掉一些异常分支之后,便是处理RPC请求的逻辑:对数据进行反序列化得到请求对象,然后进行处理。来看processRpcRequest的实现,去除一些异常分支之后的代码如下
privatevoidprocessRpcRequest(RpcRequestHeaderProto header,
DataInputStream dis)throwsWrappedRpcServerException,
InterruptedException {
Class<?extendsWritable> rpcRequestClass =
getRpcRequestWrapper(header.getRpcKind());
Writable rpcRequest;
rpcRequest = ReflectionUtils.newInstance(rpcRequestClass,conf);
rpcRequest.readFields(dis);
Call call =newCall(header.getCallId(), header.getRetryCount(),
rpcRequest,this, ProtoUtil.convert(header.getRpcKind()), header
.getClientId().toByteArray());
callQueue.put(call); // queue the call; maybe blocked here
incRpcCount(); // Increment the rpc count
}
processRpcRequest方法算是一个起到中间作用的比较重要的方法,将数据流中的二进制数据转成对应的远程调用业务Call对象,然后将Call对象放入Server远程调用业务队列(callQueue,在Server的构造函数中完成的初始化)中.
到这里,我们以Listener开头做的数据追踪,便算到了尽头。要走后续的追踪,就需要回到Server的start方法,去看它的另一个内部组件Handler的实现了(在start方法中,同时启动了Listener和Handler)。
3、Handler
Handler本身继承了Thread类,所以在Server启动的时候,将Handler作为线程来启动,我们关注其主体的run方法。去除一些异常分支之后的代码如下
privateclassHandlerextendsThread {
@Override
publicvoidrun() {
ByteArrayOutputStream buf =
newByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
while(running) {
try{
finalCallcall = callQueue.take();// pop the queue; maybe blocked here
if(call.connection.user==null) {
value = call(call.rpcKind,call .connection.protocolName,call.rpcRequest, call.timestamp);
}else{
value = call.connection.user.doAs(newPrivilegedExceptionAction<Writable>() {
@Override
publicWritable run()throwsException {
// make the call
returncall(call.rpcKind,call.connection.protocolName, call.rpcRequest,call .timestamp);
}
}
);
}
synchronized(call.connection.responseQueue) {
// setupResponse() needs to be sync'ed together with
// responder.doResponse() since setupResponse may use
// SASL to encrypt response data and SASL enforces
// its own message ordering.
setupResponse(buf,call, returnStatus, detailedErr,
value, errorClass, error);
responder.doRespond(call);
}
}
Handler在执行时候,从Server的远程调用业务队列中获取一个Call业务,执行这个远程调用业务(value=call(...)方法实现),设置远程调用业务的返回,然后进行结果返回。
4、Responder
在进行结果返回时,调用了Responder的doRespond,其实只是把要返回的数据添加到了对应的Connection的返回队列中。由于和之前的比较类似,所以,这里就不对Responder做过多的探讨。
主要说明一点,在processResponse方法中,是按照FIFO来从队列中取数据的。在进行数据发送时,如果数据没有一次发送完成,会将调用业务对象重新塞会队列中的第一个位置,用于下一次的继续发送。相关的代码如下所示
// Processes one response. Returns true if there are no more pending
// data for this channel.
//
privatebooleanprocessResponse(LinkedList<Call> responseQueue,
booleaninHandler)throwsIOException {
call = responseQueue.removeFirst();
SocketChannel channel = call.connection.channel;
//
// Send as much data as we can in the non-blocking fashion
//
intnumBytes =channelWrite(channel, call.rpcResponse);
if(numBytes < 0) {
returntrue;
}
if(!call.rpcResponse.hasRemaining()) {
}else{
//
// If we were unable to write the entire response out, then
// insert in Selector queue.
//
call.connection.responseQueue.addFirst(call);
}
}
每次都是取队列的第一个元素(removeFirst),执行写操作后,会检查数据是否已经全部发送完成。如果数据还未发送完成,则将对象继续放回队列(call.connection.responseQueue.addFirst(call))。在下一次处理返回结果时,继续发送这个处理结果。避免数据错乱。
1 0
- Hadoop RPC分析 (二) -- Server
- Hadoop NameNode之RPC Server(二)
- Hadoop源代码分析之Hadoop RPC(Server)
- Hadoop RPC源码解析——Server类(二)
- Hadoop RPC源码分析之Server
- Hadoop源码分析 RPC Server端
- Hadoop NameNode之RPC Server(一)
- hadoop------RPC的Server
- 【Hadoop】RPC Server Workflow
- Hadoop源码分析之一(RPC机制之Server)
- Hadoop源码分析之一(RPC机制之Server)
- Hadoop RPC通信Server端的流程分析
- Hadoop源码分析之一(RPC机制之Server)
- Hadoop源代码分析(三)RPC
- Hadoop RPC分析(一) -- Client
- Hadoop源代码分析【RPC】
- hadoop的RPC分析
- Hadoop RPC详细分析
- 【杭电】[1254]推箱子
- Processing 3 中手动添加系统库(processing.* )
- IntelliJ IDE运行Tomcat报错:Unable to ping server at localhost:1099
- Triplanar Projection
- Java的值传递
- Hadoop RPC分析 (二) -- Server
- 选夫婿1
- 我的博客
- Codeforces
- 由系统的内容提供器读取手机联系人信息
- VS2013下搭建SDL开发环境
- PHP阶段总结
- win7下安装wince6.0遇到问题的解决
- 第九章 事件标志组管理