前面两篇文章已经分析了provider和consumer之间的通信过程,那么还有几个问题:
- 由于请求是异步的,provider返回结果到客户端之后,consumer怎么知道该结果是哪个请求的?
- 由于请求是异步的,为何Dubbo能同步等待结果?
由于请求是异步的,provider返回结果到客户端之后,consumer怎么知道该结果是哪个请求的?
先看下第一个问题,因为provider返回结果也是一次网络请求,那么切入点自然也是Handler,看下哪个Handler处理了Response,从上篇文章中的NettyServer结构图中一个个Handler查看,发现HeaderExchangeHandler有做处理,主要是received方法
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
//....
} else if (message instanceof Response) {
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
//....
} else {
handler.received(exchangeChannel, message);
}
}catch (Exception e){
logger.error(e);
}finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
如果接收到的对象是Response类型的,那么交由handleResponse进行处理
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {// 如果不是心跳信息
DefaultFuture.received(channel, response);
}
}
DefaultFuture
public static void received(Channel channel, Response response) {
try {
// 从Map中获取id对应的DefaultFuture对象并移除
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {// 如果有,则调用doReceived方法
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
CHANNELS.remove(response.getId());
}
}
看到这里,又有几个问题 1. responseId是什么? 2. 取到的DefaultFuture又是什么?
通过搜索response id使用到的地方,在HeaderExchangeHandler#handleRequest中发现设置了值
Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
Response res = new Response(req.getId(), req.getVersion());
//...
return res;
}
response id是从请求信息request中取的,那么就是说response和request是一一对应且通过id绑定,request的id在初始化的时候会自动初始化该值
public Request() {
mId = newId();
}
private static long newId() {
// getAndIncrement()增长到MAX_VALUE时,再增长会变为MIN_VALUE,负数也可以做为ID
return INVOKE_ID.getAndIncrement();
}
private static final AtomicLong INVOKE_ID = new AtomicLong(0);
再回顾一下请求过程中,Client的request方法
public ResponseFuture request(Object request, int timeout) throws RemotingException {
// create request.
Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req);
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
在每发送一次请求,会创建一个Request,默认会带有一个递增的id,并且再创建一个DefaultFuture,并保存request和channel的信息
public DefaultFuture(Channel channel, Request request, int timeout){
this.channel = channel;
this.request = request;
this.id = request.getId();
this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// put into waiting map.
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
从构造方法可以看出,创建DefaultFuture主要是用来保存当次请求对应的Request信息和Channel信息
那么到这里,第一个问题就有了答案了,Dubbo在请求的时候,会赋予当前请求一个id,将这个id放到请求中发送出去,当收到返回结果的时候,也会带上请求的id,然后本地获取DefaultFuture,便可知道是哪次请求的了,整个流程图如下:
由于请求是异步的,为何Dubbo能同步等待结果?
在请求的时候,最后返回的只是一个Future,同步的情况下,Dubbo调用了Future的get方法
public Object get() throws RemotingException {
return get(timeout);
}
public Object get(int timeout) throws RemotingException {
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
if (! isDone()) {//response != null
long start = System.currentTimeMillis();
lock.lock();
try {
while (! isDone()) {
done.await(timeout, TimeUnit.MILLISECONDS);
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
if (! isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
return returnFromResponse();// 返回结果
}
可以看到,在调用了get方法之后,会判断isDone是否为true,如果没有,那么会不停的重试,直至成功或者超时,所以这就相当于Dubbo在发起请求之后不停的去检查是否返回,从外面看来就是同步的。
那么还有个问题就是isDone几时才会返回true,即response几时才 != null,还是回到上面Future的received方法,在通过id获取到future之后,又调用的doReceived方法
public static void received(Channel channel, Response response) {
try {
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
future.doReceived(response);
} else {
//....
}
} finally {
CHANNELS.remove(response.getId());
}
}
private void doReceived(Response res) {
lock.lock();
try {
response = res;
if (done != null) {
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
在获取到response之后,将其设置到对应的Future中并唤醒在get中的阻塞住的线程
其他
DefaultFuture中会启动一个线程不停的去扫描超时的请求
private static class RemotingInvocationTimeoutScan implements Runnable {
public void run() {
while (true) {
for (DefaultFuture future : FUTURES.values()) {
if (future == null || future.isDone()) {
continue;
}
if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {
// 如果超时了,构造一个超时Response并调用received方法
Response timeoutResponse = new Response(future.getId());
// set timeout status.
timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
// handle response.
DefaultFuture.received(future.getChannel(), timeoutResponse);
}
}
Thread.sleep(30);
}
}
}