下面讲Handler是如何处理Call请求的,简单的说就是利用动态代理做一次函数调用,将返回值发给Responser,然后由Responser发送给客户端,Handler的处理函数如下:
public void run() {
LOG.info(getName() + ": starting");
SERVER.set(Server.this);
//分配返回数据的缓冲区
ByteArrayOutputStream buf =
new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
while (running) {
try {
final Call call = callQueue.take(); // 从调用队列中拿一个出来进行处理
if (LOG.isDebugEnabled())
LOG.debug(getName() + ": has #" + call.id + " from " +
call.connection);
String errorClass = null;
String error = null;
Writable value = null;
CurCall.set(call);
//下面这里开始调用,返回值为value
try {
// Make the call as the user viaSubject.doAs, thus associating
// the call with the Subject
if (call.connection.user == null) {
value = call(call.connection.protocol, call.param,
call.timestamp);
} else {
value =
call.connection.user.doAs
(new PrivilegedExceptionAction<Writable>(){
@Override
public Writable run() throws Exception {
// 这里开始真正的调用
return call(call.connection.protocol,
call.param, call.timestamp);
}
}
);
}
} catch (Throwable e) {
LOG.info(getName()+", call "+call+": error: " + e, e);
errorClass =e.getClass().getName();
error = StringUtils.stringifyException(e);
}
CurCall.set(null);
synchronized (call.connection.responseQueue) {
// setupResponse() needs to be sync'ed togetherwith
// responder.doResponse() sincesetupResponse may use
// SASL to encrypt response data and SASLenforces
// its own message ordering.
setupResponse(buf, call,
(error == null) ? Status.SUCCESS : Status.ERROR,
value, errorClass,error);
// Discard the large buf and resetit back to
// smaller size to freeup heap
if (buf.size() > maxRespSize) {
LOG.warn("Large response size " + buf.size() + " for call " +
call.toString());
buf = new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
}
responder.doRespond(call);
}
} catch (InterruptedException e) {
if (running) { // unexpected -- log it
LOG.info(getName() + " caught: " +
StringUtils.stringifyException(e));
}
} catch (Exception e) {
LOG.info(getName() + " caught: " +
StringUtils.stringifyException(e));
}
}
LOG.info(getName() + ": exiting");
}
}
上面代码中,Call对象的调用和返回值的设置是重头戏,这个函数里一坨的日志记录和异常捕获,我们只看关键部分,执行完后返回一个Writable类型的可序列化数据,用于返回。
先看第一个:调用
public Writablecall(Class<?> protocol, Writable param, long receivedTime)
throws IOException {
try {
//这里的param是实现Writable和Invocation接口的,所以可以自由转化
Invocation call = (Invocation)param;
if (verbose) log("Call:" + call);
//获得具体的调用方法,因为我们前台执行的是ls命令,所以这里的函数为getFileInfo
Method method =
protocol.getMethod(call.getMethodName(),
call.getParameterClasses());
method.setAccessible(true);
//动态代理中常用的功能,记录调用时间,返回类型为Object
long startTime = System.currentTimeMillis();
Object value = method.invoke(instance, call.getParameters());
int processingTime = (int) (System.currentTimeMillis() -startTime);
int qTime = (int) (startTime-receivedTime);
if (LOG.isDebugEnabled()) {
LOG.debug("Served: " + call.getMethodName() +
" queueTime= " + qTime +
" procesingTime= " + processingTime);
}
rpcMetrics.addRpcQueueTime(qTime);
rpcMetrics.addRpcProcessingTime(processingTime);
rpcMetrics.addRpcProcessingTime(call.getMethodName(),processingTime);
if (verbose) log("Return:"+value);
//创建返回值并返回
return new ObjectWritable(method.getReturnType(), value);
} catch (InvocationTargetException e) {
Throwable target =e.getTargetException();
if (target instanceof IOException) {
throw (IOException)target;
} else {
IOException ioe = new IOException(target.toString());
ioe.setStackTrace(target.getStackTrace());
throw ioe;
}
} catch (Throwable e) {
if (!(e instanceof IOException)) {
LOG.error("Unexpected throwable object ", e);
}
IOException ioe = new IOException(e.toString());
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
}
}
看返回值的处理,在上面函数执行完后,会将返回值存放在Call对象的Response成员中
private void setupResponse(ByteArrayOutputStreamresponse,
Call call, Statusstatus,
Writable rv,String errorClass, String error)
throws IOException {
response.reset();
DataOutputStream out = new DataOutputStream(response);
//调用编号、成功状态、错误类型一个都不能少
out.writeInt(call.id); // write call id
out.writeInt(status.state); // write status
if (status == Status.SUCCESS) {
//返回值的写入
rv.write(out);
} else {
WritableUtils.writeString(out,errorClass);
WritableUtils.writeString(out,error);
}
if (call.connection.useWrap) {
wrapWithSasl(response, call);
}
//复制给Call对象的成员变量,然后就剩返回了
call.setResponse(ByteBuffer.wrap(response.toByteArray()));
}
上面函数执行完后,会进入responder.doRespond(call)中,在这里主要会调用Responser的processResponse进行数据返回的操作,调用关系不如上图来的直接,所以直接贴图了,呵呵
void doRespond(Call call) throws IOException{
synchronized (call.connection.responseQueue) {
//注意,这里会把call对象放入返回队列哦
call.connection.responseQueue.addLast(call);
if (call.connection.responseQueue.size() == 1) {
//开始处理返回数据
processResponse(call.connection.responseQueue, true);
}
}
}
再看下真正发送数据的函数
private int channelWrite(WritableByteChannel channel,
ByteBuffer buffer) throws IOException {
int count = (buffer.remaining() <= NIO_BUFFER_LIMIT) ?
channel.write(buffer) : channelIO(null, channel, buffer);
if (count > 0) {
rpcMetrics.incrSentBytes(count);
}
return count;
}
这个函数执行完后,客户端就能看到返回数据了
上面的演示中我们看到了在发送的时候用到了Responser对象,那么为什么还要启动一个Responser线程呢?我们看下这个线程的执行体就能理解了
public void run() {
LOG.info(getName() + ": starting");
SERVER.set(Server.this);
long lastPurgeTime = 0; // last check for old calls.
while (running) {
try {
waitPending(); // If a channel is being registered, wait.
writeSelector.select(PURGE_INTERVAL);
Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
try {
//这里会处理SelectionKey的OP_WRITE事件,做异步写操作,
//其实内部调用函数是一样的,如果网络情况不佳,那么处理OP_WRITE事件
//能有效降低网络阻塞对服务器的影响
if (key.isValid() && key.isWritable()){
doAsyncWrite(key);//
}
} catch (IOException e) {
LOG.info(getName() + ": doAsyncWrite threw exception " + e);
}
}
long now = System.currentTimeMillis();
if (now < lastPurgeTime + PURGE_INTERVAL) {
continue;
}
lastPurgeTime = now;
//
// 长时间没有处理的Call会被清除
//
LOG.debug("Checking for old callresponses.");
ArrayList<Call> calls;
// get the list of channels from list ofkeys.
synchronized (writeSelector.keys()) {
calls = new ArrayList<Call>(writeSelector.keys().size());
iter = writeSelector.keys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
Call call =(Call)key.attachment();
if (call != null && key.channel() == call.connection.channel) {
calls.add(call);
}
}
}
for(Call call : calls) {
try {
doPurge(call, now);
} catch (IOException e) {
LOG.warn("Error in purging old calls " + e);
}
}
} catch (OutOfMemoryError e) {
//
// we can run out of memory if we have toomany threads
// log the event and sleep for a minute andgive
// some thread(s) a chance to finish
//
LOG.warn("Out of Memory in server select", e);
try { Thread.sleep(60000); } catch (Exception ie) {}
} catch (Exception e) {
LOG.warn("Exception in Responder " +
StringUtils.stringifyException(e));
}
}
LOG.info("Stopping " + this.getName());
}