Dubbo配置介绍

Dubbo支持的配置默认有四种来源:

  • JVM System Properties,-D 参数;
  • Externalized Configuration,外部化配置;
  • ServiceConfig、ReferenceConfig 等编程接口采集的配置;
  • 本地配置文件 dubbo.properties;

配置的覆盖关系如下:

Dubbo 配置中心 - 图1

image

2 动态配置中心

配置中心在 Dubbo 中承担两个职责:

  1. 外部化配置。启动配置的集中式存储 (简单理解为 dubbo.properties 的外部化存储)。
  2. 服务治理。服务治理规则的存储与通知。

例如:

  1. <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>

3 ConfigCenterBean

ConfigCenterBean可以设置配置中心地址、连接超时时间、是否优先级最高等。还可以设置连接配置中心的用户名、密码。

3.1 includeSpringEnv属性

includeSpringEnv用于设置是否需要从spring的Environment对象(注意dubbo也有一个Environment对象,两个对象不是一个类)中获取配置信息。默认为falseConfigCenterBean实现了EnvironmentAware接口,在启动的时候会调用setEnvironment()方法:

  1. public void setEnvironment(Environment environment) {
  2. if (includeSpringEnv) {
  3. // Get PropertySource mapped to 'dubbo.properties' in Spring Environment.
  4. setExternalConfig(getConfigurations(getConfigFile(), environment));
  5. // Get PropertySource mapped to 'application.dubbo.properties' in Spring Environment.
  6. setAppExternalConfig(getConfigurations(StringUtils.isNotEmpty(getAppConfigFile()) ? getAppConfigFile() : ("application." + getConfigFile()), environment));
  7. }
  8. }

如果includeSpringEnv设置为true,那么将从springEnvironment对象中读取key为“dubbo.properties”和“application.dubbo.properties”的配置值,并分别设置给属性externalConfigurationappExternalConfiguration

DubboBootstrap会对Environment初始化,初始化的时候将ConfigCenterBeanexternalConfigurationappExternalConfiguration的值设置到Environment对象的externalConfigurationMapappExternalConfigurationMap

  1. public void initialize() throws IllegalStateException {
  2. ConfigManager configManager = ApplicationModel.getConfigManager();
  3. Optional<Collection<ConfigCenterConfig>> defaultConfigs = configManager.getDefaultConfigCenter();
  4. defaultConfigs.ifPresent(configs -> {
  5. for (ConfigCenterConfig config : configs) {
  6. //externalConfigurationMap和appExternalConfigurationMap是Map对象
  7. this.setExternalConfigMap(config.getExternalConfiguration());
  8. this.setAppExternalConfigMap(config.getAppExternalConfiguration());
  9. }
  10. });
  11. }

4 配置中心初始化

DubboBootstrap初始化(initialize方法)时要调用startConfigCenter方法,该方法代码如下:

  1. private void startConfigCenter() {
  2. //获取所有ConfigCenterConfig对象,ConfigCenterBean是其子类,其实这个位置获得是ConfigCenterBean对象
  3. Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
  4. if (CollectionUtils.isNotEmpty(configCenters)) {
  5. CompositeDynamicConfiguration compositeDynamicConfiguration = new CompositeDynamicConfiguration();
  6. //遍历每个ConfigCenterBean对象
  7. for (ConfigCenterConfig configCenter : configCenters) {
  8. //使用java系统属性等设置ConfigCenterBean对象的属性
  9. configCenter.refresh();
  10. //校验ConfigCenterBean对象的parameters是否合法
  11. ConfigValidationUtils.validateConfigCenterConfig(configCenter);
  12. //prepareEnvironment方法建立与配置中心的连接,拉取配置数据,并保存到本地
  13. compositeDynamicConfiguration.addConfiguration(prepareEnvironment(configCenter));
  14. }
  15. environment.setDynamicConfiguration(compositeDynamicConfiguration);
  16. }
  17. //使用配置中心的配置更新如下对象的属性:
  18. //ApplicationConfig、MonitorConfig、ModuleConfig、ProtocolConfig、RegistryConfig、
  19. //ProviderConfig、ConsumerConfig。
  20. //更新对象属性时,使用如下规则搜索配置:dubbo.类名去掉Config.id值.属性名,
  21. //比如更新id为“test”的ProviderConfig的threads属性时,
  22. //从配置中心搜索key=dubbo.provider.test.threads,如果能找到就更新threads属性。
  23. configManager.refreshAll();
  24. }
  25. private DynamicConfiguration prepareEnvironment(ConfigCenterConfig configCenter) {
  26. //ConfigCenterConfig必须配置address,否则为无效
  27. if (configCenter.isValid()) {
  28. if (!configCenter.checkOrUpdateInited()) {
  29. return null;
  30. }
  31. //构建连接配置中心的url,url中包含了ip、端口、协议等
  32. //getDynamicConfiguration根据url的协议使用SPI创建DynamicConfiguration对象
  33. //DynamicConfiguration对象建立与配置中心的连接
  34. DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
  35. //从配置中心拉取key=dubbo.properties,group=dubbo的值(这两个值都是默认值,我们可以通过修改属性configFile来修改key)
  36. String configContent = dynamicConfiguration.getProperties(configCenter.getConfigFile(), configCenter.getGroup());
  37. String appGroup = getApplication().getName();
  38. String appConfigContent = null;
  39. if (isNotEmpty(appGroup)) {
  40. //从配置中心拉取应用配置,group是应用名,key是appConfigFile的值,
  41. //如果appConfigFile=null,则使用configFile,
  42. //默认是使用configFile的值,也就是dubbo.properties
  43. appConfigContent = dynamicConfiguration.getProperties
  44. (isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
  45. appGroup
  46. );
  47. }
  48. try {
  49. environment.setConfigCenterFirst(configCenter.isHighestPriority())
  50. //将配置信息保存到Environment的Map属性中,后面的配置会覆盖之前的
  51. environment.updateExternalConfigurationMap(parseProperties(configContent));
  52. environment.updateAppExternalConfigurationMap(parseProperties(appConfigContent));
  53. } catch (IOException e) {
  54. throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
  55. }
  56. return dynamicConfiguration;
  57. }
  58. return null;
  59. }

上面的代码是创建动态配置中心并连接配置中心拉取数据。

4.1 DynamicConfiguration

DynamicConfigurationConfiguration的子接口,用于连接外部配置,具体实现类包括:

  • ZookeeperDynamicConfiguration
  • NacosDynamicConfiguration
  • ConsulDynamicConfiguration
  • FileSystemDynamicConfiguration

下面介绍ZookeeperDynamicConfiguration创建连接和监听。

4.2 ZookeeperDynamicConfiguration

Zookeeper默认所有的配置都存储在 /dubbo/config 节点,具体节点结构图如下:

Dubbo 配置中心 - 图2

image

  • namespace,用于不同配置的环境隔离;
  • config,Dubbo约定的固定节点,不可更改,所有配置和服务治理规则都存储在此节点下;
  • dubbo/application,分别用来隔离全局配置、应用级别配置:dubbo是默认group值,application对应应用名;
  • dubbo.properties,此节点的node value存储具体配置内容;

4.3 创建连接

prepareEnvironment方法通过getDynamicConfiguration方法获取与动态配置中心的连接。

  1. static DynamicConfiguration getDynamicConfiguration(URL connectionURL) {
  2. //获取连接配置中心使用的协议,下面分析以zk为例
  3. String protocol = connectionURL.getProtocol();
  4. //使用SPI加载DynamicConfigurationFactory对象,其支持的协议以及对应的类可以参见文件
  5. //org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory。
  6. DynamicConfigurationFactory factory = getDynamicConfigurationFactory(protocol);
  7. //使用DynamicConfigurationFactory创建DynamicConfiguration
  8. return factory.getDynamicConfiguration(connectionURL);
  9. }
  10. static DynamicConfigurationFactory getDynamicConfigurationFactory(String name) {
  11. Class<DynamicConfigurationFactory> factoryClass = DynamicConfigurationFactory.class;
  12. ExtensionLoader<DynamicConfigurationFactory> loader = getExtensionLoader(factoryClass);
  13. return loader.getOrDefaultExtension(name);
  14. }

下面以创建ZookeeperDynamicConfiguration为例,构造动态配置中心。

  1. ZookeeperDynamicConfiguration(URL url, ZookeeperTransporter zookeeperTransporter) {
  2. this.url = url;
  3. //构建访问配置中心的根路径,默认是:/dubbo/config/
  4. rootPath = PATH_SEPARATOR + url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + "/config";
  5. initializedLatch = new CountDownLatch(1);
  6. //创建监听器
  7. this.cacheListener = new CacheListener(rootPath, initializedLatch);
  8. //创建单个线程,用于处理被监听的事件
  9. this.executor = Executors.newFixedThreadPool(1, new NamedThreadFactory(this.getClass().getSimpleName(), true));
  10. //建立与配置中心的连接
  11. zkClient = zookeeperTransporter.connect(url);
  12. //设置监听器和监听目录
  13. zkClient.addDataListener(rootPath, cacheListener, executor);
  14. try {
  15. //可以通过ConfigCenterBean的parameters设置init.timeout的值,init.timeout表示建立链接的超时时间
  16. long timeout = url.getParameter("init.timeout", 5000);
  17. boolean isCountDown = this.initializedLatch.await(timeout, TimeUnit.MILLISECONDS);
  18. if (!isCountDown) {
  19. throw new IllegalStateException("Failed to receive INITIALIZED event from zookeeper, pls. check if url "
  20. + url + " is correct");
  21. }
  22. } catch (InterruptedException e) {
  23. logger.warn("Failed to build local cache for config center (zookeeper)." + url);
  24. }
  25. }

4.4 监听配置中心

建立与配置中心链接时,在ZookeeperDynamicConfiguration的构造方法中设置监听器CacheListenerCacheListener将监听rootPath路径。

  1. //该方法用于设置监听zk的指定目录
  2. public void addDataListener(String path, DataListener listener, Executor executor) {
  3. //listeners是一个两层map对象,类型如下:
  4. //ConcurrentMap<String, ConcurrentMap<DataListener, TargetDataListener>>
  5. //最外层的map,key是被监听的路径,内层的map,key和value都是监听器,
  6. //其区别是value可以认为是对key的封装,在本代码中key是CacheListener,value是CuratorWatcherImpl。
  7. ConcurrentMap<DataListener, TargetDataListener> dataListenerMap = listeners.get(path);
  8. if (dataListenerMap == null) {
  9. listeners.putIfAbsent(path, new ConcurrentHashMap<DataListener, TargetDataListener>());
  10. dataListenerMap = listeners.get(path);
  11. }
  12. TargetDataListener targetListener = dataListenerMap.get(listener);
  13. if (targetListener == null) {
  14. //createTargetDataListener创建目标监听器,方法见下面[1]
  15. dataListenerMap.putIfAbsent(listener, createTargetDataListener(path, listener));
  16. targetListener = dataListenerMap.get(listener);
  17. }
  18. //访问zk将targetListener注册为监听器,方法代码见[2]
  19. addTargetDataListener(path, targetListener, executor);
  20. }
  21. //[1] 以zk为配置中心为例
  22. protected CuratorZookeeperClient.CuratorWatcherImpl createTargetDataListener(String path, DataListener listener) {
  23. return new CuratorWatcherImpl(client, listener);
  24. }
  25. //[2]
  26. public List<String> addTargetChildListener(String path, CuratorWatcherImpl listener) {
  27. try {
  28. return client.getChildren().usingWatcher(listener).forPath(path);
  29. } catch (NoNodeException e) {
  30. return null;
  31. } catch (Exception e) {
  32. throw new IllegalStateException(e.getMessage(), e);
  33. }
  34. }

addDataListener方法的监听器其实是CuratorWatcherImplCuratorWatcherImpl的代码如下:

  1. static class CuratorWatcherImpl implements CuratorWatcher, TreeCacheListener {
  2. private CuratorFramework client;
  3. private volatile ChildListener childListener;
  4. private volatile DataListener dataListener;
  5. private String path;
  6. //createTargetDataListener方法调用下面的方法创建CuratorWatcherImpl对象,
  7. //从方法入参DataListener可以看出,只监听zk节点数据的变化
  8. public CuratorWatcherImpl(CuratorFramework client, DataListener dataListener) {
  9. this.dataListener = dataListener;
  10. }
  11. //...
  12. //代码删减
  13. //当数据有变化时,通知调用下面的方法
  14. //本方法主要是做类型的转换,然后调用CacheListener通知数据变化
  15. @Override
  16. public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
  17. if (dataListener != null) {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("listen the zookeeper changed. The changed data:" + event.getData());
  20. }
  21. TreeCacheEvent.Type type = event.getType();
  22. EventType eventType = null;
  23. String content = null;
  24. String path = null;
  25. switch (type) {
  26. case NODE_ADDED:
  27. eventType = EventType.NodeCreated;
  28. path = event.getData().getPath();
  29. content = event.getData().getData() == null ? "" : new String(event.getData().getData(), CHARSET);
  30. break;
  31. case NODE_UPDATED:
  32. eventType = EventType.NodeDataChanged;
  33. path = event.getData().getPath();
  34. content = event.getData().getData() == null ? "" : new String(event.getData().getData(), CHARSET);
  35. break;
  36. case NODE_REMOVED:
  37. path = event.getData().getPath();
  38. eventType = EventType.NodeDeleted;
  39. break;
  40. case INITIALIZED:
  41. eventType = EventType.INITIALIZED;
  42. break;
  43. case CONNECTION_LOST:
  44. eventType = EventType.CONNECTION_LOST;
  45. break;
  46. case CONNECTION_RECONNECTED:
  47. eventType = EventType.CONNECTION_RECONNECTED;
  48. break;
  49. case CONNECTION_SUSPENDED:
  50. eventType = EventType.CONNECTION_SUSPENDED;
  51. break;
  52. }
  53. //调用CacheListener,下面介绍该方法
  54. dataListener.dataChanged(path, content, eventType);
  55. }
  56. }
  57. }

CacheListenerdataChanged方法如下:

  1. public void dataChanged(String path, Object value, EventType eventType) {
  2. if (eventType == null) {
  3. return;
  4. }
  5. //当发生INITIALIZED类型的事件时,表示客户端缓存数据同步成功之后可以与zk服务端正常交互,
  6. //在ZookeeperDynamicConfiguration构造方法中,调用initializedLatch.await方法等待该事件,
  7. //默认等待5s,超时抛出异常
  8. if (eventType == EventType.INITIALIZED) {
  9. initializedLatch.countDown();
  10. return;
  11. }
  12. if (path == null || (value == null && eventType != EventType.NodeDeleted)) {
  13. return;
  14. }
  15. // TODO We only care the changes happened on a specific path level, for example
  16. // /dubbo/config/dubbo/configurators, other config changes not in this level will be ignored
  17. //本监听器对路径深度有检查,深度至少是四级
  18. if (path.split("/").length >= MIN_PATH_DEPTH) {
  19. //获取key值,也就是路径中最后一个“/”后面的内容
  20. String key = pathToKey(path);
  21. ConfigChangeType changeType;
  22. //本监听器只处理下面三种事件:增加、删除、修改
  23. switch (eventType) {
  24. case NodeCreated:
  25. changeType = ConfigChangeType.ADDED;
  26. break;
  27. case NodeDeleted:
  28. changeType = ConfigChangeType.DELETED;
  29. break;
  30. case NodeDataChanged:
  31. changeType = ConfigChangeType.MODIFIED;
  32. break;
  33. default:
  34. return;
  35. }
  36. //创建事件ConfigChangedEvent
  37. ConfigChangedEvent configChangeEvent = new ConfigChangedEvent(key, getGroup(path), (String) value, changeType);
  38. //通知各个监听器
  39. //CacheListener其实是一个复合监听器,包含了多个子监听器
  40. //CacheListener根据被监听路径将ConfigChangedEvent事件发送给对应的监听器
  41. Set<ConfigurationListener> listeners = keyListeners.get(path);
  42. if (CollectionUtils.isNotEmpty(listeners)) {
  43. listeners.forEach(listener -> listener.process(configChangeEvent));
  44. }
  45. }
  46. }

CacheListener是一个复合监听器,其持有一个监听器集合,当指定目录下的数据发生变化时,通知集合中的监听器,这个集合可以包含的监听器如下:

  • ServiceConfigurationListener:监听目录:/dubbo/config/dubbo/接口+ : version : goup+.configurators;
  • ProviderConfigurationListener:监听目录:/dubbo/config/dubbo/ApplicationConfig的name值+.configurators;
  • ConsumerConfigurationListener:监听目录:/dubbo/config/dubbo/ApplicationConfig的name值+.configurators;
  • ReferenceConfigurationListener:监听目录:/dubbo/config/dubbo/接口名+: version : goup+.configurators;
  • TagRouter“:监听目录:/dubbo/config/dubbo/remote.application属性值+.tag-router;
  • ServiceRouter:监听目录:/dubbo/config/dubbo/接口名+: version: goup+.condition-router;
  • AppRouter监听目录:/dubbo/config/dubbo/应用名+.condition-router

上面这些监听器都是在其构造方法中将自身作为监听器添加到CacheListener的listeners中。

下面简单介绍前四个监听器的作用:

  1. ServiceConfigurationListener:根据修改后的配置,重新发布服务;
  2. ProviderConfigurationListener:根据修改后的配置,重新发布服务;
  3. ReferenceConfigurationListener:根据修改后的配置,重新建立对远程服务的引用;
  4. ConsumerConfigurationListener:根据修改后的配置,重新建立对远程服务的引用;

因为ProviderConfigurationListenerConsumerConfigurationListener监听应用目录,如果dubbo应用发布的服务或者引用的服务比较多,则会造成dubbo修改配置有延时。

如果需要修改配置,可以在配置中心修改相应目录下的数据,这样上述监听器便监听到数据变化,进而修改本地配置。dubbo不会自动向这些目录下存储配置数据。

dubbo解析-详解配置中心_龚厂长的博客-CSDN博客