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);
            }
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 在前面的文章中,我们分析了 dubbo 从 provider 进行服务暴露,然后把服务信息注册到注册中心上面解耦 ...
    carl_zhao阅读 1,336评论 0 3
  • 在前面一篇博客中分享了 dubbo 在网络通信当中的 consumer 的发送以及接收原理。通过集群容错最终选择一...
    carl_zhao阅读 932评论 0 2
  • 遇到家人不接电话的情况,心里总是忍不住想冒火。仔细想想,有两个原因:第一,这是个老习惯的信号;第二,昨天以为今天可...
    张玉蓉zyr阅读 246评论 0 0
  • 【见证】 文/王小雪 我就这样子静静地 静静地看着你往下滴 一滴一滴地流进我的血液里 让雪白色的灯光给我做个见证 ...
    王小雪的秘密花园阅读 65评论 0 2