本文基于dubbo 2.7.5版本代码
dubbo可以通过多种方式配置,比如系统属性、配置文件、XXXConfig配置对象等。这就带来一个问题,当通过多种方式配置同一个属性时,不同方式之间该属性值的覆盖关系是什么样子的。本文来研究这个问题。
dubbo可以配置参数值的方式的有:
下图中,优先级高的配置方式可以覆盖优先级低的,其中通过系统属性设置的配置可以覆盖其他任何方式的配置,也就是使用java命令启动的时候增加-D参数配置优先级最高:
服务端配置指的是使用其他四种配置方式对服务提供者进行参数设置,这些参数会发送到注册中心,消费端从注册中心获取到这些配置数据并设置到本地。设置的参数可能仅仅对消费端起作用,比如服务超时时间。
java在启动的时候,可以使用-D选项设置参数,比如:
java -Ddubbo.application.id=server -Ddubbo.registry.address=zookeeper://localhost:2181 …(省略)
在代码里面可以通过System.getProperty(“属性名”)获取属性值。
dubbo也会使用System.getenv读取环境变量,所以我们也可以通过环境变量设置参数。但是环境变量的优先级要低于系统属性。
该方式是把参数配置放到配置中心,应用程序读取配置中心。以zk作为配置中心为例,将设置的参数做为节点的数据,dubbo读取到数据后,使用如下代码解析:
Properties properties = new Properties();
properties.load(new StringReader(content));//content便是从配置中心读取的节点数据
配置中心可以参见文章:《dubbo解析-详解配置中心》
API编程方式是设置XXXConfig对象的属性,比如设置ApplicationConfig、ServiceConfig、ReferenceConfig等对象。
可以通过注解、springboot配置文件、代码编程三种方式设置:
(1)注解:@Service(cache=“lru”,retries=3)
(2)springboot配置文件:
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
(3)代码编程(来源官网):
public static void main(String[] args) throws IOException {
ServiceConfig<GreetingsService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("first-dubbo-provider"));
service.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234"));
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());
service.export();
System.out.println("first-dubbo-provider is running.");
System.in.read();
}
在本地创建dubbo.properties文件,该文件的配置方式与springboot配置文件的规则一致。
下面从消费端启动过程分析覆盖关系是如何实现的。
首先dubbo启动的时候加载配置中心的配置数据,代码如下:
//DubboBootstrap初始化调用下面的方法
private void startConfigCenter() {
Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
if (CollectionUtils.isNotEmpty(configCenters)) {
CompositeDynamicConfiguration compositeDynamicConfiguration = new CompositeDynamicConfiguration();
for (ConfigCenterConfig configCenter : configCenters) {
//代码删减
//prepareEnvironment见下方[1]处
//prepareEnvironment从配置中心获取配置数据,并设置到Environment对象
compositeDynamicConfiguration.addConfiguration(prepareEnvironment(configCenter));
}
environment.setDynamicConfiguration(compositeDynamicConfiguration);
}
//更新XXXConfig对象属性值,代码见[2]处
configManager.refreshAll();
}
[1]
private DynamicConfiguration prepareEnvironment(ConfigCenterConfig configCenter) {
if (configCenter.isValid()) {
//代码删减
DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
String configContent = dynamicConfiguration.getProperties(configCenter.getConfigFile(), configCenter.getGroup());
String appGroup = getApplication().getName();
String appConfigContent = null;
if (isNotEmpty(appGroup)) {
appConfigContent = dynamicConfiguration.getProperties
(isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
appGroup
);
}
try {
//configContent和appConfigContent均是从配置中心获取的数据,获取后设置到Environment对象中
environment.setConfigCenterFirst(configCenter.isHighestPriority());
environment.updateExternalConfigurationMap(parseProperties(configContent));
environment.updateAppExternalConfigurationMap(parseProperties(appConfigContent));
} catch (IOException e) {
throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
}
return dynamicConfiguration;
}
return null;
}
[2]
public void refreshAll() {
write(() -> {
//更新XXXConfig对象,下面的每个refresh方法都会调用[3]处代码
getApplication().ifPresent(ApplicationConfig::refresh);
getMonitor().ifPresent(MonitorConfig::refresh);
getModule().ifPresent(ModuleConfig::refresh);
getProtocols().forEach(ProtocolConfig::refresh);
getRegistries().forEach(RegistryConfig::refresh);
getProviders().forEach(ProviderConfig::refresh);
getConsumers().forEach(ConsumerConfig::refresh);
});
}
[3]
public void refresh() {
Environment env = ApplicationModel.getEnvironment();
try {
CompositeConfiguration compositeConfiguration = env.getConfiguration(getPrefix(), getId());//getConfiguration方法代码见[6]处
Configuration config = new ConfigConfigurationAdapter(this);
//env.isConfigCenterFirst()默认是true
if (env.isConfigCenterFirst()) {
//下面的config表示使用API方式设置的属性
[5]
compositeConfiguration.addConfiguration(4, config);
} else {
compositeConfiguration.addConfiguration(2, config);
}
//代码删减,删减的代码功能是调用compositeConfiguration的getXXX方法设置属性
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
[6]
public CompositeConfiguration getConfiguration(String prefix, String id) {
//compositeConfiguration内部有List对象,addConfiguration方法将入参添加到该List对象中
CompositeConfiguration compositeConfiguration = new CompositeConfiguration();
compositeConfiguration.addConfiguration(this.getSystemConfig(prefix, id));//getSystemConfig获取系统属性,该配置方式优先级最高
compositeConfiguration.addConfiguration(this.getEnvironmentConfig(prefix, id));//getEnvironmentConfig获取环境变量,方法内部使用System.getenv方法获取指定的属性值
compositeConfiguration.addConfiguration(this.getAppExternalConfig(prefix, id));//getAppExternalConfig获取配置中心的属性
compositeConfiguration.addConfiguration(this.getExternalConfig(prefix, id));//getExternalConfig获取配置中心的属性
compositeConfiguration.addConfiguration(this.getPropertiesConfig(prefix, id));//getPropertiesConfig获取本地配置文件中的属性
return compositeConfiguration;
}
getConfiguration方法聚合了三种配置方式设置的属性,CompositeConfiguration的List属性中各个元素的下标就表示了配置方式的优先级,下标越小优先级越高,再来看上面代码[6]处,默认情况下执行[6]处代码,也就是说通过API方式设置的属性会放在List属性中下标为4的位置,即优先级比本地配置文件dubbo.properties配置方式高一级。
ServiceConfig和ReferenceConfig对象属性的更新也是调用上面代码[3]处完成的。
上面的代码根据各个配置方式设置的属性完成了对XXXConfig对象的更新,之后服务端和消费端就根据这些XXXConfig对象的属性值去暴露服务和引用远程服务。
还有一点需要注意,ApplicationConfig、ModuleConfig、ConsumerConfig等对象有一些同名的属性,那么这里也存在属性覆盖的问题。消费端各个对象优先级如下:
ApplicationConfig
ApplicationConfig
public class ClusterUtils {
//代码有删减
//入参remoteUrl是从注册中心获取的服务端参数组成的URL对象,
//入参localMap是消费端属性配置的汇总
//mergeUrl方法的返回值是服务端和消费端配置融合后的结果
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) {
map.putAll(remoteMap);//将服务端参数设置到map中
map.remove(THREAD_NAME_KEY);
map.remove(DEFAULT_KEY_PREFIX + THREAD_NAME_KEY);
map.remove(THREADPOOL_KEY);
map.remove(DEFAULT_KEY_PREFIX + THREADPOOL_KEY);
map.remove(CORE_THREADS_KEY);
map.remove(DEFAULT_KEY_PREFIX + CORE_THREADS_KEY);
map.remove(THREADS_KEY);
map.remove(DEFAULT_KEY_PREFIX + THREADS_KEY);
map.remove(QUEUES_KEY);
map.remove(DEFAULT_KEY_PREFIX + QUEUES_KEY);
map.remove(ALIVE_KEY);
map.remove(DEFAULT_KEY_PREFIX + ALIVE_KEY);
map.remove(Constants.TRANSPORTER_KEY);
map.remove(DEFAULT_KEY_PREFIX + Constants.TRANSPORTER_KEY);
}
if (localMap != null && localMap.size() > 0) {
Map<String, String> copyOfLocalMap = new HashMap<>(localMap);
if(map.containsKey(GROUP_KEY)){
copyOfLocalMap.remove(GROUP_KEY);
}
if(map.containsKey(VERSION_KEY)){
copyOfLocalMap.remove(VERSION_KEY);
}
copyOfLocalMap.remove(RELEASE_KEY);
copyOfLocalMap.remove(DUBBO_VERSION_KEY);
copyOfLocalMap.remove(METHODS_KEY);
copyOfLocalMap.remove(TIMESTAMP_KEY);
copyOfLocalMap.remove(TAG_KEY);
//在服务端参数的基础上,使用消费端配置覆盖
map.putAll(copyOfLocalMap);
map.put(REMOTE_APPLICATION_KEY, remoteMap.get(APPLICATION_KEY));
String remoteFilter = remoteMap.get(REFERENCE_FILTER_KEY);
String localFilter = copyOfLocalMap.get(REFERENCE_FILTER_KEY);
if (remoteFilter != null && remoteFilter.length() > 0
&& localFilter != null && localFilter.length() > 0) {
map.put(REFERENCE_FILTER_KEY, remoteFilter + "," + localFilter);
}
String remoteListener = remoteMap.get(INVOKER_LISTENER_KEY);
String localListener = copyOfLocalMap.get(INVOKER_LISTENER_KEY);
if (remoteListener != null && remoteListener.length() > 0
&& localListener != null && localListener.length() > 0) {
map.put(INVOKER_LISTENER_KEY, remoteListener + "," + localListener);
}
}
return remoteUrl.clearParameters().addParameters(map);
}
}
本文参考:
http://dubbo.apache.org/zh-cn/docs/user/configuration/configuration-load-process.html