探究 dubbo接口超时时间的赋值逻辑,包含Consumer,Provider的合并。

  • 首先,我们都知道 dubbo 调用,consumer 可以配置接口超时时间,provider 也可以配置超时时间,但是最终会以 consumer 为准,这里还有一个 MethodConfig 的概念。
  • 具体的优先级体现为 conusmer.MethodConfig.timeout > provider.MethodConfig.timeout > conusmer.ServiceBean.timeout

源码

  • provider.xml
   
    <bean id="innerStockServiceExport" class="com.alibaba.dubbo.config.spring.ServiceBean">
        <property name="interface" value="com.yit.stock.api.inner.InnerStockService"/>
        <property name="ref" ref="innerStockServiceImpl"/>
        <property name="application" ref="dubboApplicationConfig"/>
        <property name="registry" ref="dubboRegistryConfig"/>
        <property name="protocol" ref="dubboProtocolConfig"/>
        <property name="version" value="${dubbo.reference.version}"/>
        <property name="timeout" value="${dubbo.export.timeout}"/>
        <property name="retries" value="0"/>
        <property name="methods">
            <list>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="initVirtualStock"/>
                    <property name="timeout" value="4000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="batchFreezeStock"/>
                    <property name="timeout" value="6000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="batchCancelStock"/>
                    <property name="timeout" value="6000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="batchConsumeStock"/>
                    <property name="timeout" value="6000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="cancelForConsumedOrder"/>
                    <property name="timeout" value="30000"/>
                </bean>
            </list>
        </property>
    </bean>
  • provider.xml

    <bean id="innerStockService" class="com.alibaba.dubbo.config.spring.ReferenceBean">
        <property name="interface" value="com.yit.stock.api.inner.InnerStockService"/>
        <property name="application" ref="dubboApplicationConfig"/>
        <property name="registry" ref="dubboRegistryConfig"/>
        <property name="timeout" value="3000"/>
        <property name="check" value="false"/>
        <property name="version" value="${dubbo.reference.version}"/>
        <property name="methods">
            <list>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="batchFreezeStock"/>
                    <property name="timeout" value="16000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="splitFreezeStock"/>
                    <property name="timeout" value="6000"/>
                </bean>
                <bean class="com.alibaba.dubbo.config.MethodConfig">
                    <property name="name" value="finishStockIn"/>
                    <property name="timeout" value="16000"/>
                </bean>
            </list>
        </property>
    </bean>

加载本地的配置信息构建 URL

    com.alibaba.dubbo.config.ReferenceConfig#init

    private void init() {
        if (initialized) {
            return;
        }
        initialized = true;

        ..... 省略一部分

        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, consumer, Constants.DEFAULT_KEY);
        appendParameters(map, this);
        String prifix = StringUtils.getServiceKey(map);

        // 这里拿到本地 consumer.xml 中 MethodConfig 的信息初始化 key: method.timeout, value: 毫秒数到 map
        if (methods != null && methods.size() > 0) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                appendAttributes(attributes, method, prifix + "." + method.getName());
                checkAndConvertImplicitConfig(method, map, attributes);
            }
        }
        //attributes通过系统context进行存储.
        StaticContext.getSystemContext().putAll(attributes);
        ref = createProxy(map);
    }
  • 我们可以看到此时的map只有comsumer.xml 的配置信息


    此时 URL 的信息.png
  • 与远程配置进行 merge
    其实我们关注的就是 RegistryDirectory.mergeUrl 中的 ClusterUtils.mergeUrl(providerUrl, queryMap);
    providerUrl :远程的接口配置
    queryMap : 本地配置URL 中的 map
    ClusterUtils.mergeUrl 中的实现就是依赖了 map.put() 的实现,先put provider,再put local。最终达到了以本地MethodConfig 为准的目的

    com.alibaba.dubbo.registry.integration.RegistryDirectory#mergeUrl

    /**
     * 合并url参数 顺序为override > -D >Consumer > Provider
     * @param providerUrl
     * @param overrides
     * @return
     */
    private URL mergeUrl(URL providerUrl){
        // 其实我们关注的就是这一部分
        providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // 合并消费端参数
        
        List<Configurator> localConfigurators = this.configurators; // local reference
        if (localConfigurators != null && localConfigurators.size() > 0) {
            for (Configurator configurator : localConfigurators) {
                providerUrl = configurator.configure(providerUrl);
            }
        }
        
        providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // 不检查连接是否成功,总是创建Invoker!
        
        ... 省略一部分

        return providerUrl;
    }

  
    public static URL mergeUrl(URL remoteUrl, Map<String, String> localMap) {
        Map<String, String> map = new HashMap<String, String>();
        Map<String, String> remoteMap = remoteUrl.getParameters();
        
        
        if (remoteMap != null && remoteMap.size() > 0) {
            // 先put remote
            map.putAll(remoteMap);
            
            //线程池配置不使用提供者的
            map.remove(Constants.THREAD_NAME_KEY);
            map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.THREAD_NAME_KEY);
            ... 省略一部分
        }
        
        if (localMap != null && localMap.size() > 0) {
            // 再put local          
            map.putAll(localMap);
        }
   
            ... 省略一部分      
        }

        return remoteUrl.clearParameters().addParameters(map);
    }
  • 我们可以看到 merge 后的map 已经多出了一些provider.xml 的配置信息


    merge后的map.png

获取 timeout 的地方

  • 从URL 中的 parameters 中根据 method.timeout 或者 timeout 为key 取出对应的数值
// 重点就是这句话
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
    com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke    

    @Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);
        
        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout) ;
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                return new RpcResult();
            } else {
                RpcContext.getContext().setFuture(null);
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

    public int getMethodParameter(String method, String key, int defaultValue) {
        String methodKey = method + "." + key;
        Number n = getNumbers().get(methodKey);
        if (n != null) {
            return n.intValue();
        }
        String value = getMethodParameter(method, key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        int i = Integer.parseInt(value);
        getNumbers().put(methodKey, i);
        return i;
    }

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

推荐阅读更多精彩内容