avatar

Dubbo源码分析之服务注册发布(五)

上一篇我们说到ServiceConfig.doExport()方法,那么我们今天就来说一下ServiceConfig.doExoprtUrls()方法。

首先从方法名上就可以看出这个方法内部主要做什么。我们一步一步的深入进去,抽丝剥茧的把Dubbo服务导出和注册给搞明白。下面我们一起进入正题吧。

前情回顾

在上一篇,我们简单的入了个Dubbo服务注册的门。

刚开始在InitializingBean.afterPropertiesSet()方法最后看到了个export()方法的调用,于是我们就一路跟到了ServiceConfig.export()方法。

ServiceConfig.export()方法中,我们可以看到Dubbo是怎样实现延迟导出的。

最后,不管是延迟导出还是不延迟导出,都是调用的 doExport()方法,最后在doExport()方法中调用doExportUrls()来导出服务。那么我们今天就一起来看看doExportUrls()方法。

ServiceConfig.doExportUrls()

首先我们一起来概览一下方法内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void doExportUrls() {
//加载注册中心地址
List<URL> registryURLs = loadRegistries(true);

//取所有配置的协议,每个协议都需要导出
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
//导出url
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}

代码不多,如果for循环整体算一行代码的话,那该方法就只有2行代码。2行代码一共做了2件事,我们一起来看看。

加载注册中心地址

我们看来看一眼loadRegistries()方法,注意该方法是在AbstractInteraceConfig类中, 这里传了个true,这里有什么含义呢?其代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
//遍历配置的注册中心地址
for (RegistryConfig config : registries) {
//注册中心的地址
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
//如果地址不可用,就不进if体
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
//组装参数
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
//添加运行时参数,比如:版本号、协议版本等
appendRuntimeParameters(map);
//如果map中没有protocol字段,就设置一个默认的dubbo进去
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
//组成URL.这里的PROTOCOL_KEY的值默认为dubbo。这里的address是注册中心的地址
List<URL> urls = UrlUtils.parseURLs(address, map);

for (URL url : urls) {
url = URLBuilder.from(url)
//这里的getProtocol的值就是上面URL的协议部分
//这里相当备份一下真正的协议,下面将把protocol替换为registry
.addParameter(REGISTRY_KEY, url.getProtocol())
//
.setProtocol(REGISTRY_PROTOCOL)
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}

上面的代码我大体给了些注释,现在我们来通过几个问题细看他们。

registries是个啥?

我们可以看到,代码第二行就是判断registries是不是为空,然后决定下面的流程。那么这个registries是个啥?

还记得我们在InitializingBean.afterPropertiesSet()中有段代码么?具体是在第5个if条件那里点击此处参考文章。最后我们有个super.setRegistries()方法的调用。在这个方法中,会把我们解析好的List<RegistryConfig>保存到AbstractInteraceConfigregistries属性中。

所以这里的registries就是我们解析的<dubbo:registry>标签(可以配置多个)。

如果我配置了<dubbo:registry>标签,但是我没有指定注册中心地址会怎样?

从上面代码上看,如果你配置的address是空的,则会默认取ANYHOST_VALUE。这个地址的值就是0.0.0.0

在组装参数前,还有个NO_AVAILABLE的判断,那么NO_AVAILABLE值是什么?

NO_AVAILABLE的值为N/A

重头戏,它是怎么组装参数的,为什么要组装参数?

我们先来说第二个问题,为什么要组装参数。我记得在Dubbo的第一篇我就说过:Dubbo是通过URL驱动,也就是说,里面所有用来交互的东西都是URL,比如存到注册中心的数据都是URL形式。Dubbo把配置信息带入到URL的参数中,这样在后面任何个地方,只要需要某个配置,我就可以从URL中取出来。

然后我们再来细看第一个问题,这块就不一行一行的看源码了,我们待会debug下,看看结果就行。从源码上有如下步骤:

  • 所有的参数都会放到一个Map。首先是把application、config信息加入到Map中
  • 添加运行时参数,比如版本号、协议版本等信息
  • 添加协议信息,也就是PROTOCOL_KEY,如果没有配置协议,协议就默认为dubbo
  • 转成URL

下面我们debug下,看看转成的URL是什么。

来解读下上面组成的URL吧。multicast://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=2068&qos-port=22222&timestamp=1590070559480

  • 上面注册中心的地址是multicast://224.5.6.7:1234,这个地址是dubbo-demo里面默认配置的,我这里就暂时没改。
  • org.apache.dubbo.registry.RegistryServiceRegistryService的全限定类名,也就是上面代码中map.put(PATH_KEY, RegistryService.class.getName());的效果。
  • ?后面就是我们的参数了。application=demo-providerqos-port=22222就是appendParameters(map, application);的效果,这两个都是ApplicationConfig的属性。
  • dubbo=2.0.2timestamp=1590070559480是在appendRuntimeParameters(map);这里put进去的,在Dubbo V2.7.2这个版本中,有个常量定义,值就是2.0.2,有兴趣的童鞋可以点进去看一眼。

最后,我们来看一下这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//这里的urls我们已经知道是什么了,就是上面的URL。
for (URL url : urls) {
url = URLBuilder.from(url)
//这里的getProtocol的值就是上面URL的协议部分,
//相对上面的URL来说,执行完getProtocol()后,返回的是multicast
//这里相当备份一下真正的协议。
.addParameter(REGISTRY_KEY, url.getProtocol())
//设置协议为registry
.setProtocol(REGISTRY_PROTOCOL)
.build();
//把URL加入到list中
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}

下面我们来断点一下,看看最后经过一系列操作后build()到的URL是什么。

也就是registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=3196&qos-port=22222&registry=multicast&timestamp=1590071948476

然后我们对比下,发现协议从multicast变成了registry,然后增加一个registry=multicast的参数。

总结

经过上面一系列的操作后,我们得到了一个List,里面是URL。如果有多注册中心,就会有多个URL,List中的元素也会有多个。

根据现在的情况,返回的List中的值为:registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=3196&qos-port=22222&registry=multicast&timestamp=1590071948476

注意:这个URL很重要,后面的所有操作,都是基于这个URL来搞事情。

现在我们的URL已经get到了,下面我们一起来看看是怎么导出的吧。

按协议导出

按惯例,先上代码:

1
2
3
4
5
6
7
8
9
10
11
//遍历我们通过<dubbo:protocol>标签配置的所有协议
for (ProtocolConfig protocolConfig : protocols) {
//通过path、group、version组装成一个key
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
//创建providerModel对象。
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
//把providerModel加入到一个ConcurrentMap中
ApplicationModel.initProviderModel(pathKey, providerModel);
//导出url
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}

我们通过debug方式来看看pathKey的结果是什么。如下:

这个不就是我们的接口的权限类名么?那我们现在来说一下URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version)到底是怎么得到结果的。

  • 首先,他是调用getContextPath()方法, 这个方法返回的是一个Optional对象,现在getContextPath()返回的是emptyOptional
  • 然后再调用Optional.map()方法。因为是emptyOptionnal,在map()方法中有判断,返回的还是一个emptyOptional(这块具体可以点进去看一眼)。
  • 最后调用Optional.orElse()方法。参数是path。而这个path就是我们解析到的接口的全限定类名。

然后接着往下面看,会创建一个ProviderModel对象,最后把这个ProviderModel对象放入到一个CocurrentHashMap中。这块感兴趣的童鞋可以进去看看。这里的用意我们暂时不清楚,等后面分析到了再说吧。

最后一步,调用doExportUrlsFor1Protocol()做导出。这个方法我们留到下篇在谈。换句话说,该篇到这里就结束了。

从上面的代码来看,我们在xml中配置了多少协议,就会调用doExportUrlsFor1Protocol()方法多少次,换而言之,就是有多个协议,导出多少次。

总结

emm。好像没啥可以总结的。这部分就是把一些配置转成URL形式,然后遍历协议调用doExportUrlsFor1Protocol()做导出。难度系数不大,但最好还是配合debug去看,因为我们并不需要每行代码都去过一遍,都去揣摩下作者的心思,没有必要把时间花费在一些不重要的代码上。

好了,感谢大家观看,后面可能时间会拖一点,有可能不能一个礼拜一篇了(虽然我知道这已经很慢了…)。

有错误之处,还请大佬么不吝赐教。拜拜~下篇再会。


评论