Dubbo源码分析----DefaultFuture

前面两篇文章已经分析了provider和consumer之间的通信过程,那么还有几个问题:

  1. 由于请求是异步的,provider返回结果到客户端之后,consumer怎么知道该结果是哪个请求的?
  2. 由于请求是异步的,为何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,便可知道是哪次请求的了,整个流程图如下:
image.png

由于请求是异步的,为何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);
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/u013160932/article/details/81155265