Dubbo源码解析——dubbo的配置覆盖

Dubbo源码解析——dubbo的配置覆盖

前言

之前有人在社区里提问说,为什么provider端使用netty4作为transport属性,但是consumer端启动失败了,不是说provider配置会覆盖consumer端的么?

这个需求其实也非常正常,如果我们要升级dubbo集群使用netty4作为传输方式,如果能够配置覆盖,只要改改provider即可,比较方便。但是实际上是不会配置透传的。

能够透传的配置,在dubbo用户手册上列举了几个,比如timeoutretries等,透传的原因文档上也给了:以timeout为例,provider端比consumer端更知道执行需要的时间。基本上都是方法级使用的一些参数。需要注意的是,protocol也是会透传的,consumer端只能被动接受,不同于其他配置项,protocol是会覆盖consumer端配置的protocol的。

Debug

这里来看下具体的实现方式,之前虽然没有细细debug过,但是大概也能猜到是通过url从zk上传递实现的。

看代码+debug之前,先明确一个概念,dubbo虽然建议provider配置超时时间,但是provider如果同时和consumer配置timeout,那么会使用consumer配置的,这里我从文档上扣一张图过来说明一下timeout的配置优先级:

文档上写的很清楚,如果consumer端配置了timeout,那么就会使用consumer端配置的,如果没有,就再去provider端找。

我们先验证一下,看看内部优先级,Consumer配置:

这里我圈出了三个timeout配置。

如果按照图上的顺序,应该使用1500这个配置项。我们看下实际效果,这里我用的dubbo协议,一次rpc调用会走到DubboInvoker#doInvoke里,也就是这里设置了超时。

我们看下debug的效果:

确实如我们之前看到那样的,timeout1500

再看看如果ProviderConsumer共存timeout的情况下的情况。

C(以后用C表示Consumer,P表示Provider)的配置:

1
<dubbo:reference timeout="2000" id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

P的配置:

1
<dubbo:service timeout="2500" interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>

效果:

确实是C的配置覆盖了P的配置。

我们在看下,如果C配置的protocolrmi协议,而P配置的protocoldubbo会怎么样:

断点依然跑到了DubboInvoker里。这说明我们的RMI协议并没有生效,C只能强行接受P配置的protocol

代码实现

我们看看Dubbo怎么实现的配置覆盖。之前我的文章中其实已经说过了,Inovker实际上是被维护在一个叫Directory的结构里。DubboInvoker为例,timeout被取出来是代码中的:int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);Constants.DEFAULT_TIMEOUT是1000,也就是如果哪里都不配置timeout,就是1s。

getUrl就是返回这个DubboInvoker对应的url。这个Url什么时候被设置进去的呢,之前我们也猜测过了,通过zk透传。这里我们可以大体定位一下,就是Consumer启动时创建Invoker,url(timeout)就被设置好了。看下Directory#refreshInvoker方法,这个方法就是根据ProviderUrl生成Invoker的。其中有这么一句:Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);

继续追进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// sth...
for (URL providerUrl : urls) {

// sth...
// 合并url
URL url = mergeUrl(providerUrl);

// sth...

if (invoker == null) {
try {
boolean enabled = true;
// 这里根据上面的那个合并之后的url创建invoker
if (enabled) {
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
//sth ...
}
}
}
// sth...
return newUrlInvokerMap;

这里我们可以看到,首先是mergeUrl方法,看这个名字,大概就知道是把本地的url和远端的url进行合并,这里应该会进行timeout覆盖,因为consumerprovider配置了两个timeout

然后就根据合成好的url生成Invoker了,这里会把merge以后的Url放到Invoker里,将来真正进行rpc调用的时候,timeout就是从这个url里获取的。

看一下mergeUrl方法,第一行就是merge操作:

1
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap);

这里这个queryMap,是:this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));获取的,里面包含了一些本地设置的属性,比如timeout,我举个栗子,C的配置:<dubbo:reference timeout="2000" id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>的时候,queryMap的内容:

我们可以看到,里面有C端配置的超时时间。

那么合并其实就是把P端的这些参数(比如timeout)和C端的参数合并,生成一个最终的Url。

我们看下ClusterUtils#mergeUrl方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Map<String, String> map = new HashMap<String, String>();
Map<String, String> remoteMap = remoteUrl.getParameters();

// map中先放remoteMap
if (remoteMap != null && remoteMap.size() > 0) {
map.putAll(remoteMap);
// do sth...
}

// sth...
if (localMap != null && localMap.size() > 0) {
map.putAll(localMap);
}
// sth...
return remoteUrl.clearParameters().addParameters(map);

map就是将来Url的一些配置参数,timeout就在里面。remoteMap是P端配置的参数。localMap是本地配置的参数,是C端配置的。

代码里,先放remoteMap,后放localMap,相同键覆盖,所以就会出现C端timeout会覆盖P端timeout的现象了~但是如果localMap里没有timeout,则会使用remoteMaptimeout,及使用P配置的timeout

后记

Dubbo已经重新开始维护,很多新特性都会被加入进来,希望大家多多参与dubbo社区建设,在github上积极反馈问题,提出新想法~