前言
在一套完整的分布式系统中,client端向server端发起一个请求,然后client等待此请求被server端处理完毕,然后接受到serve的返回结果。自此一个请求就算作是被处理完了。这种block等待处理结果的请求处理行为在我们日常的系统中十分的常见。但是这种处理方式的一个明显弊端是,未处理完成的请求势必会占住server端的处理资源。因此一般常见的改进做法是提高server端的Handler数量,来提高服务端的请求并发处理能力,这种做法是比较简单直接的。但其实这里还有另外一个方向点的优化,是否能够提高单个请求的处理时间来做优化呢?比如一些已经被处理完毕的请求,但是正处于返回response结果的,这也是会占着Handler资源的。因为返回response操作也是在请求被处理环节的一部分。假设说我们能将回复请求的阶段从处理请求方法中拆分出去,通过延时返回的方式,毫无疑问,这也会在一定程度上提高server端的throughput。本文笔者来聊聊关于RPC Server请求的回复延时处理以及Hadoop RPC Server是如何做这部分优化的。
Server端延时请求回复的优劣势
按照前面我们所说的,如果将server端的<请求即刻处理->请求即时回复>变为<请求即刻处理->请求延时恢复>,它会给整个系统带来怎样的变化呢?
以下是其所带来的好的一面和不好的一面:
优势:
增大系统整体的throughput,因为请求结果回复变为了延时异步的方式,这相当于提早释放了Handler的资源,让Handler能够马上接下来处理其它客户端的请求。
弊端:
- 请求回复的延时处理意味着server端于client的open connection会增多,因为client的请求回复还没有被执行,这些连接并没有被关闭。
- Server端因为此改动接受了过多的请求,导致请求回复阶段成为bottleneck。
Hadoop RPC Server的请求延时回复处理
下面我们通过Hadoop RPC Server内部目前已经优化了的请求回复处理例子,来具体了解下这部分的处理逻辑。
样例一:增加延时请求计数的处理
这个改动源自Hadoop社区JIRA:HADOOP-10300:Allowed deferred sending of call responses。
它的一个主要思路是这样的:
1) 在每个PRC call里面多添加了一个请求等待的计数值,初始值为1
2)正常情况下,Server端在处理完请求后,会执行sendResponse方法,然后会将上述计数值做减1操作,然后执行请求回复操作。
3)但是,如果我们想要做请求的延时回复处理,我们可以额外执行一个postponeResponse的方法来增大请求回复等待的计数值。这样的话,在正常逻辑中的sendResponse则不会实际执行请求回复操作,它只做计数值的减操作。只有再第二次Server被触发执行了sendResponse后,才会执行请求回复操作。
相关代码如下:
/** 请求回复等待计数值 */
private AtomicInteger responseWaitCount = new AtomicInteger(1);
...
/**
* Allow a IPC response to be postponed instead of sent immediately
* after the handler returns from the proxy method. The intended use
* case is freeing up the handler thread when the response is known,
* but an expensive pre-condition must be satisfied before it's sent
* to the client.
*/
@InterfaceStability.Unstable
@InterfaceAudience.LimitedPrivate({
"HDFS"})
public void postponeResponse() {
// 执行延时回复处理,将计数值加1
int count = responseWaitCount.incrementAndGet();
assert count > 0 : "response has already been sent";
}
@InterfaceStability.Unstable
@InterfaceAudience.LimitedPrivate({
"HDFS"})
public void sendResponse() throws IOException {
// 执行请求回复操作时,减小计数值
int count = responseWaitCount.decrementAndGet();
assert count >= 0 : "response has already been sent";
// 如果计数值为0了,则进行实际回复返回操作,否则不进行response信息的返回
if (count == 0) {
assert rpcResponse != null : "response has not been set";
connection.sendResponse(this);
}
}
下面是对应的testcase:
// Test that IPC calls can be marked for a deferred response.
// call 0: immediate
// call 1: immediate
// call 2: delayed with wait for 1 sendResponse, check if blocked
// call 3: immediate, proves handler is freed
// call 4: delayed with wait for 2 sendResponses, check if blocked
// call 2: sendResponse, should return
// call 4: sendResponse, should remain blocked
// call 5: immediate, prove handler is still free
// call 4: sendResponse, expect it to return
@Test(timeout=10000)
public void testDeferResponse() throws IOException, InterruptedException {
final AtomicReference<Call> deferredCall = new AtomicReference<Call>();
final AtomicInteger count = new AtomicInteger();
final Writable wait0 = new IntWritable(0);
final Writable wait1 = new IntWritable(1);
final Writable wait2 = new IntWritable(2);
// use only 1 handler to prove it's freed after every call
Server server = new Server(ADDRESS, 0, IntWritable.class, 1, conf){
@Override
public Writable call(RPC.RpcKind rpcKind, String protocol,
Writable waitCount, long receiveTime) throws IOException {
Call call = Server.getCurCall().get();
int wait = ((IntWritable)waitCount).get();
// 根据传入的wait次数值,做postponeResponse的处理,意为这个call需要做额外对应次数的sendResponse方法才会有结果返回
while (wait-- > 0) {
call.postponeResponse();
deferredCall.set(call);
}
return new IntWritable(count.getAndIncrement());
}
};
server.start();
final InetSocketAddress address = NetUtils.getConnectAddress(server);
final Client client = new Client(IntWritable.class, conf);
Call[] waitingCalls = new Call[2];
// calls should return immediately, check the sequence number is
// increasing
assertEquals(0,
((IntWritable)client.call(wait0, address)).