配置方法
API配置
以Java编码的方式组织配置,Dubbo3配置API详解 :https://dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/config/api/#bootstrap-api
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();
}
xml配置
以XML方式配置各种组件,支持与Spring无缝集成
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>
<dubbo:application name="demo-provider"/>
<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
<dubbo:provider token="true"/>
<bean id="demoService" class="org.apache.dubbo.samples.basic.impl.DemoServiceImpl"/>
<dubbo:service interface="org.apache.dubbo.samples.basic.api.DemoService" ref="demoService"/>
</beans>
Annotation 结合SpringBoot配置
以注解方式暴露服务和引用服务接口,支持与Spring无缝集成
- 注解有 @DubboService、@DubboReference 与 @EnableDubbo。
其中 @DubboService 与 @DubboReference 用于标记 Dubbo 服务
@EnableDubbo 启动 Dubbo 相关配置并指定 Spring Boot 扫描包路径。
- 配置文件 application.properties 或 application.yml
@Service
与 @Reference
注解从 3.0 版本开始就已经废弃,改用 @DubboService,@DubboReference
,以区别于 Spring 的 @Service,@Reference
注解
@DubboService(version = "1.0.0", group = "dev", timeout = 5000,registry="zk-registry")
@DubboReference(version = "1.0.0", group = "dev", timeout = 5000)
注解中的参数有限,如果需要可以使用dubbo.properties 或者javaconfig代替,或者通过SpringBoot的配置文件结合
SpringBoot配置文件
spring.application.name=dubbo--provider-sample
dubbo.scan.base-packages=com.alibaba.boot.dubbo.demo.provider.service
# Dubbo Application
## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}
# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=12345
## Dubbo Registry
dubbo.registry.address=N/A
## DemoService version
demo.service.version=1.0.0
JavaConfig
JavaConfig即使用 @Bean的方式添加到Spring容器中
@Configuration
public class Configuration {
@Bean
public ServiceConfig demoService() {
ServiceConfig service = new ServiceConfig();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setGroup("dev");
service.setVersion("1.0.0");
Map<String, String> parameters = new HashMap<>();
service.setParameters(parameters);
return service;
}
}
dubbo.properties
参考
dubbo.service.org.apache.dubbo.springboot.demo.DemoService.timeout=5000
dubbo.service.org.apache.dubbo.springboot.demo.DemoService.parameters=[{myKey:myValue},{anotherKey:anotherValue}]
dubbo.reference.org.apache.dubbo.springboot.demo.DemoService.timeout=6000
配置加载流程
- JVM System Properties,JVM -D 参数
- System environment,JVM进程的环境变量
- Externalized Configuration,外部化配置,从配置中心读取
- Application Configuration,应用的属性配置,从Spring应用的Environment中提取"dubbo"打头的属性集
- API / XML /注解等编程接口采集的配置可以被理解成配置来源的一种,是直接面向用户编程的配置采集方式
- 从classpath读取配置文件 dubbo.properties
关于dubbo.properties属性:
- 如果在 classpath 下有超过一个 dubbo.properties 文件,比如,两个 jar 包都各自包含了 dubbo.properties,dubbo 将随机选择一个加载,并且打印错误日志。
- Dubbo 可以自动加载 classpath 根目录下的 dubbo.properties,但是你同样可以使用 JVM 参数来指定路径:
-Ddubbo.properties.file=xxx.properties
。
常用配置组件如下:
- application: Dubbo应用配置
- registry: 注册中心
- protocol: 服务提供者RPC协议
- config-center: 配置中心
- metadata-report: 元数据中心
- service: 服务提供者配置
- reference: 远程服务引用配置
- provider: service的默认配置或分组配置
- consumer: reference的默认配置或分组配置
- module: 模块配置
- monitor: 监控配置
- metrics: 指标配置
- ssl: SSL/TLS配置
属性覆盖
发生属性覆盖可能有两种情况,并且二者可能是会同时发生的:
- 不同配置源配置了相同的配置项
- 相同配置源,但在不同层次指定了相同的配置项
不同配置源
按配置加载流程 从上到下覆盖, JVM -D 优先级最高,dubbo.properties 优先级最低
相同配置源
属性覆盖是指用配置的属性值覆盖config bean实例的属性,类似Spring PropertyOverrideConfigurer
的作用。
但与PropertyOverrideConfigurer
的不同之处是,Dubbo的属性覆盖有多个匹配格式,优先级从高到低依次是:
#1. 指定id的实例级配置 dubbo.{config-type}s.{config-id}.{config-item}={config-item-value}
#2. 指定name的实例级配置 dubbo.{config-type}s.{config-name}.{config-item}={config-item-value}
#3. 应用级配置(单数配置) dubbo.{config-type}.{config-item}={config-item-value}
属性覆盖处理流程:
按照优先级从高到低依次查找,如果找到此前缀开头的属性,则选定使用这个前缀提取属性,忽略后面的配置。
配置加载源码解析
DefaultApplicationDeployer#initialize()
@Override
public void initialize() {
if (initialized) {
return;
}
// Ensure that the initialization is completed when concurrent calls
synchronized (startLock) {
if (initialized) {
return;
}
// register shutdown hook
registerShutdownHook();
startConfigCenter();
loadApplicationConfigs();
initModuleDeployers();
// @since 2.7.8
startMetadataCenter();
initialized = true;
if (logger.isInfoEnabled()) {
logger.info(getIdentifier() + " has been initialized!");
}
}
}
始化过程中会先启动配置中心配置信息处理,然后 调用加载初始化应用程序配置方法loadApplicationConfigs()
进行配置加载
Dubbo框架的配置项比较繁多,为了更好地管理各种配置,将其按照用途划分为不同的组件,最终所有配置项都会汇聚到URL中,传递给后续处理模块。
配置信息的初始化回顾
在创建dubbo环境时,ModuleEnvironment环境信息 getApplicationModel().getModelEnvironment()获取对象时,使用spi扩展加载ModuleEnvironment,会触发 ExtensionLoader#initExtension ()
对象如下代码所示:
public ModuleEnvironment(ModuleModel moduleModel) {
super(moduleModel);
this.moduleModel = moduleModel;
this.applicationDelegate = moduleModel.getApplicationModel().getModelEnvironment();
}
@Override
public ModuleEnvironment getModelEnvironment() {
if (moduleEnvironment == null) {
moduleEnvironment = (ModuleEnvironment) this.getExtensionLoader(ModuleExt.class)
.getExtension(ModuleEnvironment.NAME);
}
return moduleEnvironment;
}
private void initExtension(T instance) {
if (instance instanceof Lifecycle) {
Lifecycle lifecycle = (Lifecycle) instance;
lifecycle.initialize();
}
}
属性加载
Environment中属性的初始化方法 initialize()
这个初始化方法对应ModuleEnvironment的父类型Environment中的初始化方法如下
@Override
public void initialize() throws IllegalStateException {
if (initialized.compareAndSet(false, true)) {
//加载在JVM或者环境变量指定的dubbo.properties配置文件 配置的key为dubbo.properties.file ,如果未指定则查找类路径下的dubbo.properties
this.propertiesConfiguration = new PropertiesConfiguration(scopeModel);
//系统JVM参数的配置无需我们来加载到内存,系统已经加载好了放到了System中,我们只需System.getProperty(key)来调用
this.systemConfiguration = new SystemConfiguration();
//系统环境变量的配置,无需我们来加载到内存,系统已经加载好了放到了System中,我们只需System.getenv(key)来获取就可以
this.environmentConfiguration = new EnvironmentConfiguration();
//从远程配置中心的全局配置获取对应配置
this.externalConfiguration = new InmemoryConfiguration("ExternalConfig");
//从远程配置中心的应用配置获取对应配置
this.appExternalConfiguration = new InmemoryConfiguration("AppExternalConfig");
//应用内的配置比如: Spring Environment/PropertySources/application.properties
this.appConfiguration = new InmemoryConfiguration("AppConfig");
//加载迁移配置,用户在JVM参数或者环境变量中指定的dubbo.migration.file,如果用户未指定测尝试加载类路径下的dubbo-migration.yaml
loadMigrationRule();
}
}
dubbo.properties配置文件加载解析原理
如前面所示:
//加载在JVM或者环境变量指定的dubbo.properties配置文件 配置的key为dubbo.properties.file ,如果未指定则查找类路径下的dubbo.properties
this.propertiesConfiguration = new PropertiesConfiguration(scopeModel);
下面就直接提构造器的PropertiesConfiguration代码了:
public PropertiesConfiguration(ScopeModel scopeModel) {
this.scopeModel = scopeModel;
refresh();
}
public void refresh() {
//配置获取的过程是借助工具类ConfigUtils来获取的
properties = ConfigUtils.getProperties(scopeModel.getClassLoaders());
}
继续看ConfigUtils的getProperties方法:
public static Properties getProperties(Set<ClassLoader> classLoaders) {
//这个配置的KEY是dubbo.properties.file System.getProperty是从JVM参数中获取配置的 一般情况下我们在启动Java进程的时候会指定Dubbo配置文件 如配置:
//-Ddubbo.properties.file=/dubbo.properties
String path = System.getProperty(CommonConstants.DUBBO_PROPERTIES_KEY);
if (StringUtils.isEmpty(path)) {
//优先级最高的JVM参数拿不到数据则从 环境变量中获取,这个配置key也是dubbo.properties.file System.getenv是从环境变量中获取数据
//例如我们在环境变量中配置 dubbo.properties.file=/dubbo.properties
path = System.getenv(CommonConstants.DUBBO_PROPERTIES_KEY);
if (StringUtils.isEmpty(path)) {
//如果在JVM参数和环境变量都拿不到这个配置文件的路径我们就用默认的吧
//默认的路径是类路径下的资源文件 这个路径是: dubbo.properties
path = CommonConstants.DEFAULT_DUBBO_PROPERTIES;
}
}
return ConfigUtils.loadProperties(classLoaders, path, false, true);
}
路径获取之后加载详细的配置内容:
ConfigUtils的loadProperties代码如下:
ConfigUtils.loadProperties(classLoaders, path, false, true);
代码如下:
public static Properties loadProperties(Set<ClassLoader> classLoaders, String fileName, boolean allowMultiFile, boolean optional) {
Properties properties = new Properties();
// add scene judgement in windows environment Fix 2557
//检查文件是否存在 直接加载配置文件如果加载到了配置文件则直接返回
if (checkFileNameExist(fileName)) {
try {
FileInputStream input = new FileInputStream(fileName);
try {
properties.load(input);
} finally {
input.close();
}
} catch (Throwable e) {
logger.warn("Failed to load " + fileName + " file from " + fileName + "(ignore this file): " + e.getMessage(), e);
}
return properties;
}
//为什么会有下面的逻辑呢,如果仅仅使用上面的加载方式只能加载到本系统下的配置文件,无法加载封装在jar中的根路径的配置
Set<java.net.URL> set = null;
try {
List<ClassLoader> classLoadersToLoad = new LinkedList<>();
classLoadersToLoad.add(ClassUtils.getClassLoader());
classLoadersToLoad.addAll(classLoaders);
//这个方法loadResources在扩展加载的时候说过
set = ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad).values().stream().reduce(new LinkedHashSet<>(), (a, i) -> {
a.addAll(i);
return a;
});
} catch (Throwable t) {
logger.warn("Fail to load " + fileName + " file: " + t.getMessage(), t);
}
if (CollectionUtils.isEmpty(set)) {
if (!optional) {
logger.warn("No " + fileName + " found on the class path.");
}
return properties;
}
if (!allowMultiFile) {
if (set.size() > 1) {
String errMsg = String.format("only 1 %s file is expected, but %d dubbo.properties files found on class path: %s",
fileName, set.size(), set);
logger.warn(errMsg);
}
// fall back to use method getResourceAsStream
try {
properties.load(ClassUtils.getClassLoader().getResourceAsStream(fileName));
} catch (Throwable e) {
logger.warn("Failed to load " + fileName + " file from " + fileName + "(ignore this file): " + e.getMessage(), e);
}
return properties;
}
logger.info("load " + fileName + " properties file from " + set);
for (java.net.URL url : set) {
try {
Properties p = new Properties();
InputStream input = url.openStream();
if (input != null) {
try {
p.load(input);
properties.putAll(p);
} finally {
try {
input.close();
} catch (Throwable t) {
}
}
}
} catch (Throwable e) {
logger.warn("Fail to load " + fileName + " file from " + url + "(ignore this file): " + e.getMessage(), e);
}
}
return properties;
}
完整的配置加载流程这里用简单的话描述下:
-
项目内配置查询
-
- 路径查询
-
-
- 从JVM参数中获取配置的 dubbo.properties.file配置文件路径
- 如果前面未获取到路径则从环境变量参数中获取配置的dubbo.properties.file配置文件路径
- 如果前面未获取到路径则使用默认路径dubbo.propertie
-
-
- 配置加载
-
-
- 将路径转为FileInputStream 然后使用Properties加载
-
-
依赖中的配置扫描查询
-
- 使用类加载器扫描所有资源URL
- url转InputStream 如 url.openStream() 然后使用Properties加载
加载JVM参数的配置
这里我们继续看SystemConfiguration配置的加载 这个直接看下代码就可以了:
这个类型仅仅是使用System.getProperty来获取JVM配置即可
public class SystemConfiguration implements Configuration {
@Override
public Object getInternalProperty(String key) {
return System.getProperty(key);
}
public Map<String, String> getProperties() {
return (Map) System.getProperties();
}
}
加载环境变量参数的配置
这里我们来看EnvironmentConfiguration
public class EnvironmentConfiguration implements Configuration {
@Override
public Object getInternalProperty(String key) {
String value = System.getenv(key);
if (StringUtils.isEmpty(value)) {
value = System.getenv(StringUtils.toOSStyleKey(key));
}
return value;
}
public Map<String, String> getProperties() {
return System.getenv();
}
}
内存配置的封装:InmemoryConfiguration
这里我们看下InmemoryConfiguration的设计,这个直接看代码吧内部使用了一个LinkedHashMap来存储配置
public class InmemoryConfiguration implements Configuration {
private String name;
// stores the configuration key-value pairs
private Map<String, String> store = new LinkedHashMap<>();
public InmemoryConfiguration() {
}
public InmemoryConfiguration(String name) {
this.name = name;
}
public InmemoryConfiguration(Map<String, String> properties) {
this.setProperties(properties);
}
@Override
public Object getInternalProperty(String key) {
return store.get(key);
}
/**
* Add one property into the store, the previous value will be replaced if the key exists
*/
public void addProperty(String key, String value) {
store.put(key, value);
}
/**
* Add a set of properties into the store
*/
public void addProperties(Map<String, String> properties) {
if (properties != null) {
this.store.putAll(properties);
}
}
/**
* set store
*/
public void setProperties(Map<String, String> properties) {
if (properties != null) {
this.store = properties;
}
}
public Map<String, String> getProperties() {
return store;
}
}
Dubbo迁移新版本的配置文件加载dubbo-migration.yaml
这个配置文件的文件名字为:dubbo-migration.yaml
private void loadMigrationRule() {
//JVM参数的dubbo.migration.file配置
String path = System.getProperty(CommonConstants.DUBBO_MIGRATION_KEY);
if (StringUtils.isEmpty(path)) {
//环境变量的dubbo.migration.file配置
path = System.getenv(CommonConstants.DUBBO_MIGRATION_KEY);
if (StringUtils.isEmpty(path)) {
//默认的迁移配置文件 dubbo-migration.yaml
path = CommonConstants.DEFAULT_DUBBO_MIGRATION_FILE;
}
}
this.localMigrationRule = ConfigUtils.loadMigrationRule(scopeModel.getClassLoaders(), path);
}
初始化加载应用配置
加载配置涉及到了配置优先级的处理,
下面来看加载配置代码 loadApplicationConfigs()方法
private void loadApplicationConfigs() {
//发布器还是不处理配置加载的逻辑还是交给配置管理器
configManager.loadConfigs();
}
配置管理器加载配置:
@Override
public void loadConfigs() {
// application config has load before starting config center
// load dubbo.applications.xxx
//加载应用配置
loadConfigsOfTypeFromProps(ApplicationConfig.class);
// load dubbo.monitors.xxx
//加载监控配置
loadConfigsOfTypeFromProps(MonitorConfig.class);
// load dubbo.metrics.xxx
//加载指标监控配置
loadConfigsOfTypeFromProps(MetricsConfig.class);
// load multiple config types:
// load dubbo.protocols.xxx
//加载协议配置
loadConfigsOfTypeFromProps(ProtocolConfig.class);
// load dubbo.registries.xxx
loadConfigsOfTypeFromProps(RegistryConfig.class);
// load dubbo.metadata-report.xxx
//加载元数据配置
loadConfigsOfTypeFromProps(MetadataReportConfig.class);
// config centers has bean loaded before starting config center
//loadConfigsOfTypeFromProps(ConfigCenterConfig.class);
//刷新配置
refreshAll();
//检查配置
checkConfigs();
// set model name
if (StringUtils.isBlank(applicationModel.getModelName())) {
applicationModel.setModelName(applicationModel.getApplicationName());
}
}