問題描述
近期開發項目,將Dubbo的配置全部外部化到動態配置中心。這里配置中心我使用的是Apollo。
@Configuration
public class DubboConfig {
@Bean
public ConfigCenterConfig configCenterConfig() {
ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
configCenterConfig.setAddress("apollo.xxxxx.com");
configCenterConfig.setProtocol("apollo");
configCenterConfig.setNamespace("dubbo");
configCenterConfig.setGroup(null);
return configCenterConfig;
}
}
這里使用dubbo這個namespace。
其余配置全部配置在這個里面。
然后遇到一個大坑:
這里項目中定義了一個Service,也就是作為Provider提供了版本為provider.auth.version的服務。
package io.github.slankka.provider;
@Service(version = "${provider.auth.version:1.0}")
@Slf4j
public class AuthApiService implements IAuthApi
項目啟動之后,注冊到注冊中心的版本是1.0。
在dubbo命名空間中定義了provider.auth.version=20000,但是啟動還是1.0
問題分析
Apollo啟動模式分為兩種,一種是在 BeanDefinition階段就將配置注入Spring容器內的Environment。另一種是在PostBeanFactory的時候啟動。
而Dubbo啟動的時候,所有的Service是ServiceBean的實例對應生成的代理類,所有的Reference是ReferenceBean的實例對應生成的代理類。
因此如果這里使用placeholder要被Spring識別,那么將必須選用第一種啟動方式。
問題解決
在本地項目的application.properties中配置:
apollo.bootstrap.enabled = true
apollo.bootstrap.namespaces = application,dubbo
注意,這里apollo.bootstrap.namespaces 中加入了dubbo,這里有一個疑問:
之前配置Dubbo的時候不是定義了ConfigCenterConfig的namespace了嗎,為什么還要定義一次。
configCenterConfig.setNamespace("dubbo");
原因是:
Dubbo的ServiceBean在被Spring工廠構建出來的時候,就需要這個變量。但如果沒有配置dubbo到apollo.bootstrap.namespaces,Spring會報錯。
如果接口上寫了默認版本,上例是1.0,則Spring不會報錯,但是構建出來的對應ServiceBean中的版本將會是1.0,而Dubbo啟動之后依然不會修改這個版本。
新的問題
為什么Dubbo的namespace中配置了這個變量,而且Dubbo在啟動早起階段就已經拉到了這些變量,但版本仍舊沒有發生改變?
看一下Dubbo代碼:
//package org.apache.dubbo.common.config;
public abstract class AbstractPrefixConfiguration implements Configuration {
protected String id;
protected String prefix;
public AbstractPrefixConfiguration(String prefix, String id) {
if (StringUtils.isNotEmpty(prefix) && !prefix.endsWith(".")) {
this.prefix = prefix + ".";
} else {
this.prefix = prefix;
}
this.id = id;
}
@Override
public Object getProperty(String key, Object defaultValue) {
Object value = null;
if (StringUtils.isNotEmpty(prefix)) {
if (StringUtils.isNotEmpty(id)) {
value = getInternalProperty(prefix + id + "." + key);
}
if (value == null) {
value = getInternalProperty(prefix + key);
}
} else {
value = getInternalProperty(key);
}
return value != null ? value : defaultValue;
}
}
已知Dubbo是通過Configuration 的getProperty獲取version等等這些屬性,
這里可以看到用prefix + key的方式作為 property的名字來獲取變量的值。
經過Debug發現,Dubbo的 AbstractPrefixConfiguration類的prefix正好是接口的FQCN,在本例中為:
AbstractPrefixConfiguration.prefix=dubbo.service.io.github.slankka.provider.IAuthApi.
那么key=version
因此可以推斷出,Dubbo的外部化配置指定的Namespace中,如果要指定版本,且希望經過Dubbo的Environment處理,那么一定要用這種形式:
dubbo.service.io.github.slankka.provider.IAuthApi.version=20000
總結
- 如果使用placeholder的方式定義Service版本,那么根據習慣,要確保這些變量放在Spring啟動階段就能讀到的地方。
- 如果要在Dubbo的namespace中定義,被Dubbo處理,那么要符合Dubbo的命令規則。dubbo.service.xxxxxxx.version等等這種形式。
- 如果使用placeholder的方式定義,但希望被Apollo直接處理,那么需要配置:
apollo.bootstrap.enabled = true
apollo.bootstrap.namespaces = application,dubbo
那么什么時候使用 placeholder什么時候使用 dubbo.service.
答案很顯然是,如果多個接口都用共用同一個版本變量進行設置,用Apollo+Spring的方式進行處理。如果每一個接口都配置不同的版本,可以用Dubbo的方式定義。
進一步了解Dubbo和Apollo集成的遇到的有趣問題
解決Dubbo 2.7.3版本使用ConfigCenterConfig集成Apollo No Provider found的問題