This is an automated email from the ASF dual-hosted git repository. liujun pushed a commit to branch dev-metadata in repository https://gitbox.apache.org/repos/asf/incubator-dubbo.git
commit 6f618226c179f76cf921f4792df6878320917ef1 Author: ken.lj <ken.lj...@gmail.com> AuthorDate: Tue Nov 27 17:29:09 2018 +0800 try different zookeeper implementations --- .../configcenter/AbstractDynamicConfiguration.java | 31 +++-- .../dubbo/configcenter/ConfigChangeEvent.java | 16 +-- .../dubbo/configcenter/DynamicConfiguration.java | 2 + .../support/nop/NopDynamicConfiguration.java | 9 +- .../mock/MockDynamicConfiguration.java | 23 ++++ ....apache.dubbo.configcenter.DynamicConfiguration | 1 + .../support/apollo/ApolloDynamicConfiguration.java | 48 ++++--- .../pom.xml | 4 +- .../zookeeper}/ArchaiusDynamicConfiguration.java | 60 ++++----- .../sources/ZooKeeperConfigurationSource.java | 4 +- ....apache.dubbo.configcenter.DynamicConfiguration | 1 + .../pom.xml | 16 +-- .../support/zookeeper/CacheListener.java | 107 +++++++++++++++ .../zookeeper/ZookeeperDynamicConfiguration.java | 144 ++++++++++++++++++++ ....apache.dubbo.configcenter.DynamicConfiguration | 1 + .../ZookeeperDynamicConfigurationTest.java | 150 +++++++++++++++++++++ .../dubbo-configcenter-zookeeper/pom.xml | 30 +++-- ....apache.dubbo.configcenter.DynamicConfiguration | 3 +- .../ZookeeperDynamicConfigurationTest.java | 150 +++++++++++++++++++++ dubbo-configcenter/pom.xml | 6 +- 20 files changed, 699 insertions(+), 107 deletions(-) diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/AbstractDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/AbstractDynamicConfiguration.java index 258ce52..6fcf8c3 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/AbstractDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/AbstractDynamicConfiguration.java @@ -26,8 +26,8 @@ import java.util.concurrent.ConcurrentMap; * Dynamic configuration template class. The concrete implementation needs to provide implementation for three methods. * * @see AbstractDynamicConfiguration#getTargetConfig(String, String, long) - * @see AbstractDynamicConfiguration#addListener(String, ConfigurationListener) - * @see AbstractDynamicConfiguration#createTargetListener(String, ConfigurationListener) + * @see AbstractDynamicConfiguration#addConfigurationListener(TargetListener, ConfigurationListener) + * @see AbstractDynamicConfiguration#createTargetListener(String) */ public abstract class AbstractDynamicConfiguration<TargetListener> extends AbstractConfiguration implements DynamicConfiguration { @@ -36,7 +36,7 @@ public abstract class AbstractDynamicConfiguration<TargetListener> extends Abstr protected URL url; // One key can register multiple target listeners, but one target listener only maps to one configuration listener - private ConcurrentMap<String, ConcurrentMap<ConfigurationListener, TargetListener>> targetListeners = + protected ConcurrentMap<String, TargetListener> targetListeners = new ConcurrentHashMap<>(); public AbstractDynamicConfiguration() { @@ -49,11 +49,8 @@ public abstract class AbstractDynamicConfiguration<TargetListener> extends Abstr @Override public void addListener(String key, ConfigurationListener listener) { - ConcurrentMap<ConfigurationListener, TargetListener> listeners = targetListeners.computeIfAbsent(key, - k -> new ConcurrentHashMap<>()); - TargetListener targetListener = listeners.computeIfAbsent(listener, - k -> createTargetListener(key, listener)); - addTargetListener(key, targetListener); + TargetListener targetListener = targetListeners.computeIfAbsent(key, this::createTargetListener); + addConfigurationListener(key, targetListener, listener); } @Override @@ -88,6 +85,13 @@ public abstract class AbstractDynamicConfiguration<TargetListener> extends Abstr } } + @Override + public void removeListener(String key) { + + } + + protected abstract void recover(); + /** * Fetch dynamic configuration from backend config storage. If timeout exceeds, exception should be thrown. * @@ -101,20 +105,19 @@ public abstract class AbstractDynamicConfiguration<TargetListener> extends Abstr /** * Register a native listener to the backend config storage so that Dubbo has chance to get notified when the * value changes. - * - * @param key property key listener is interested. - * @param listener native listener for the backend config storage + * @param key + * @param targetListener Implementation dependent listener, such as, zookeeper watcher, Apollo listener, ... + * @param configurationListener Listener in Dubbo that will handle notification. */ - protected abstract void addTargetListener(String key, TargetListener listener); + protected abstract void addConfigurationListener(String key, TargetListener targetListener, ConfigurationListener configurationListener); /** * Create a native listener for the backend config storage, eventually ConfigurationListener will get notified once * the value changes. * * @param key property key the native listener will listen on - * @param listener ConfigurationListener instance * @return native listener for the backend config storage */ - protected abstract TargetListener createTargetListener(String key, ConfigurationListener listener); + protected abstract TargetListener createTargetListener(String key); } diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/ConfigChangeEvent.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/ConfigChangeEvent.java index 403ec75..068f21d 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/ConfigChangeEvent.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/ConfigChangeEvent.java @@ -25,16 +25,14 @@ public class ConfigChangeEvent { private String key; private String newValue; private ConfigChangeType changeType; - private ConfigType type; - public ConfigChangeEvent(String key, String value, ConfigType type) { - this(key, value, type, ConfigChangeType.MODIFIED); + public ConfigChangeEvent(String key, String value) { + this(key, value, ConfigChangeType.MODIFIED); } - public ConfigChangeEvent(String key, String value, ConfigType type, ConfigChangeType changeType) { + public ConfigChangeEvent(String key, String value, ConfigChangeType changeType) { this.key = key; this.newValue = value; - this.type = type; this.changeType = changeType; } @@ -61,12 +59,4 @@ public class ConfigChangeEvent { public void setChangeType(ConfigChangeType changeType) { this.changeType = changeType; } - - public ConfigType getType() { - return type; - } - - public void setType(ConfigType type) { - this.type = type; - } } diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java index 83faee6..83ff936 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java @@ -41,6 +41,8 @@ public interface DynamicConfiguration extends Configuration { */ void addListener(String key, ConfigurationListener listener); + void removeListener(String key); + /** * Get the configuration mapped to the given key * diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java index 1dc0794..1250233 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java @@ -32,12 +32,12 @@ public class NopDynamicConfiguration extends AbstractDynamicConfiguration { } @Override - protected void addTargetListener(String key, Object o) { + protected void addConfigurationListener(String key, Object targetListener, ConfigurationListener configurationListener) { // no-op } @Override - protected Object createTargetListener(String key, ConfigurationListener listener) { + protected Object createTargetListener(String key) { return null; } @@ -45,4 +45,9 @@ public class NopDynamicConfiguration extends AbstractDynamicConfiguration { protected Object getInternalProperty(String key) { return null; } + + @Override + protected void recover() { + + } } diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/mock/MockDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/mock/MockDynamicConfiguration.java new file mode 100644 index 0000000..012322d --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/mock/MockDynamicConfiguration.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.mock; + +/** + * + */ +public class MockDynamicConfiguration { +} diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration b/dubbo-configcenter/dubbo-configcenter-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration new file mode 100644 index 0000000..944a089 --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration @@ -0,0 +1 @@ +mock=org.apache.dubbo.configcenter.support.mock.MockDynamicConfiguration \ No newline at end of file diff --git a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java index 8c056dc..0c0d95c 100644 --- a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java @@ -22,7 +22,6 @@ import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigChange; - import org.apache.dubbo.common.Constants; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.logger.Logger; @@ -33,15 +32,13 @@ import org.apache.dubbo.configcenter.ConfigChangeEvent; import org.apache.dubbo.configcenter.ConfigChangeType; import org.apache.dubbo.configcenter.ConfigurationListener; -import java.util.Collections; - -import static org.apache.dubbo.configcenter.ConfigType.CONFIGURATORS; -import static org.apache.dubbo.configcenter.ConfigType.ROUTERS; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; /** * Apollo implementation, https://github.com/ctripcorp/apollo */ -public class ApolloDynamicConfiguration extends AbstractDynamicConfiguration<ConfigChangeListener> { +public class ApolloDynamicConfiguration extends AbstractDynamicConfiguration<ApolloDynamicConfiguration.ApolloListener> { private static final Logger logger = LoggerFactory.getLogger(ApolloDynamicConfiguration.class); private static final String APOLLO_ENV_KEY = "env"; private static final String APOLLO_ADDR_KEY = "apollo.meta"; @@ -104,7 +101,7 @@ public class ApolloDynamicConfiguration extends AbstractDynamicConfiguration<Con } /** - * This method will used by Configuration to get valid value at runtime. + * This method will be used by Configuration to get valid value at runtime. * The group is expected to be 'app level', which can be fetched from the 'config.appnamespace' in url if necessary. * But I think Apollo's inheritance feature of namespace can solve the problem . */ @@ -114,21 +111,31 @@ public class ApolloDynamicConfiguration extends AbstractDynamicConfiguration<Con } @Override - protected void addTargetListener(String key, ConfigChangeListener listener) { - this.dubboConfig.addChangeListener(listener, Collections.singleton(key)); + protected void addConfigurationListener(String key, ApolloListener listener, ConfigurationListener configurationListener) { + listener.addListener(configurationListener); + } + + @Override + protected ApolloListener createTargetListener(String key) { + ApolloListener listener = new ApolloListener(); + this.dubboConfig.addChangeListener(listener); + return listener; } @Override - protected ConfigChangeListener createTargetListener(String key, ConfigurationListener listener) { - return new ApolloListener(listener); + protected void recover() { + // Apollo will handle things well. } - private class ApolloListener implements ConfigChangeListener { + public class ApolloListener implements ConfigChangeListener { - private ConfigurationListener listener; + private Set<ConfigurationListener> listeners = new CopyOnWriteArraySet<>(); - ApolloListener(ConfigurationListener listener) { - this.listener = listener; + ApolloListener() { + } + + public void addListener(ConfigurationListener configurationListener) { + this.listeners.add(configurationListener); } @Override @@ -140,13 +147,10 @@ public class ApolloDynamicConfiguration extends AbstractDynamicConfiguration<Con change.getOldValue() + ", the empty rule will not take effect."); return; } - // TODO Maybe we no longer need to identify the type of change. Because there's no scenario that - // a callback will subscribe for both configurators and routers - if (change.getPropertyName().endsWith(Constants.CONFIGURATORS_SUFFIX)) { - listener.process(new ConfigChangeEvent(key, change.getNewValue(), CONFIGURATORS, getChangeType(change))); - } else { - listener.process(new ConfigChangeEvent(key, change.getNewValue(), ROUTERS, getChangeType(change))); - } + + listeners.forEach( + listener -> listener.process(new ConfigChangeEvent(key, change.getNewValue(), getChangeType(change))) + ); } } diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml b/dubbo-configcenter/dubbo-configcenter-archaius/pom.xml similarity index 94% copy from dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml copy to dubbo-configcenter/dubbo-configcenter-archaius/pom.xml index 8e3d7fe..a122d72 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml +++ b/dubbo-configcenter/dubbo-configcenter-archaius/pom.xml @@ -22,10 +22,10 @@ <artifactId>dubbo-configcenter</artifactId> <version>2.7.0-SNAPSHOT</version> </parent> - <artifactId>dubbo-configcenter-zookeeper</artifactId> + <artifactId>dubbo-configcenter-archaius</artifactId> <packaging>jar</packaging> <name>${project.artifactId}</name> - <description>The zookeeper implementation of the configcenter api</description> + <description>The archaius implementation of the config-center api</description> <dependencies> <dependency> diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/ArchaiusDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ArchaiusDynamicConfiguration.java similarity index 74% rename from dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/ArchaiusDynamicConfiguration.java rename to dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ArchaiusDynamicConfiguration.java index d739009..e0c64e3 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/ArchaiusDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ArchaiusDynamicConfiguration.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.configcenter.support.archaius; +package org.apache.dubbo.configcenter.support.zookeeper; import com.netflix.config.ConfigurationManager; import com.netflix.config.DynamicPropertyFactory; @@ -24,26 +24,29 @@ import org.apache.dubbo.common.Constants; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.ConcurrentHashSet; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.configcenter.AbstractDynamicConfiguration; import org.apache.dubbo.configcenter.ConfigChangeEvent; import org.apache.dubbo.configcenter.ConfigChangeType; import org.apache.dubbo.configcenter.ConfigType; import org.apache.dubbo.configcenter.ConfigurationListener; -import org.apache.dubbo.configcenter.support.archaius.sources.ZooKeeperConfigurationSource; +import org.apache.dubbo.configcenter.support.zookeeper.sources.ZooKeeperConfigurationSource; + +import java.util.Set; import static org.apache.dubbo.common.Constants.CONFIG_NAMESPACE_KEY; -import static org.apache.dubbo.configcenter.support.archaius.sources.ZooKeeperConfigurationSource.ARCHAIUS_CONFIG_CHECK_KEY; -import static org.apache.dubbo.configcenter.support.archaius.sources.ZooKeeperConfigurationSource.ARCHAIUS_CONFIG_ROOT_PATH_KEY; -import static org.apache.dubbo.configcenter.support.archaius.sources.ZooKeeperConfigurationSource.ARCHAIUS_SOURCE_ADDRESS_KEY; -import static org.apache.dubbo.configcenter.support.archaius.sources.ZooKeeperConfigurationSource.DEFAULT_CONFIG_ROOT_PATH; +import static org.apache.dubbo.configcenter.support.zookeeper.sources.ZooKeeperConfigurationSource.ARCHAIUS_CONFIG_CHECK_KEY; +import static org.apache.dubbo.configcenter.support.zookeeper.sources.ZooKeeperConfigurationSource.ARCHAIUS_CONFIG_ROOT_PATH_KEY; +import static org.apache.dubbo.configcenter.support.zookeeper.sources.ZooKeeperConfigurationSource.ARCHAIUS_SOURCE_ADDRESS_KEY; +import static org.apache.dubbo.configcenter.support.zookeeper.sources.ZooKeeperConfigurationSource.DEFAULT_CONFIG_ROOT_PATH; /** * Archaius supports various sources and it's extensiable: JDBC, ZK, Properties, ..., so should we make it extensiable? * FIXME: we should get rid of Archaius or move it to eco system since Archaius is out of maintenance, instead, we * should rely on curator-x-async which looks quite promising. */ -public class ArchaiusDynamicConfiguration extends AbstractDynamicConfiguration<Runnable> { +public class ArchaiusDynamicConfiguration extends AbstractDynamicConfiguration<ArchaiusDynamicConfiguration.ArchaiusListener> { private static final Logger logger = LoggerFactory.getLogger(ArchaiusDynamicConfiguration.class); public ArchaiusDynamicConfiguration() { @@ -107,37 +110,35 @@ public class ArchaiusDynamicConfiguration extends AbstractDynamicConfiguration<R } @Override - protected void addTargetListener(String key, Runnable runnable) { + protected void addConfigurationListener(String key, ArchaiusListener targetListener, ConfigurationListener configurationListener) { + targetListener.addListener(configurationListener); + } + + @Override + protected ArchaiusListener createTargetListener(String key) { + ArchaiusListener archaiusListener = new ArchaiusListener(key); DynamicStringProperty prop = DynamicPropertyFactory.getInstance() .getStringProperty(key, null); - prop.addCallback(runnable); + prop.addCallback(archaiusListener); + return archaiusListener; } @Override - protected Runnable createTargetListener(String key, ConfigurationListener listener) { - return new ArchaiusListener(key, listener); + protected void recover() { + // FIXME will Archaius recover automatically? } - private class ArchaiusListener implements Runnable { - private ConfigurationListener listener; + public class ArchaiusListener implements Runnable { + private Set<ConfigurationListener> listeners = new ConcurrentHashSet<>(); private String key; private ConfigType type; - public ArchaiusListener(String key, ConfigurationListener listener) { + public ArchaiusListener(String key) { this.key = key; - this.listener = listener; - // Maybe we no longer need to identify the type of change. Because there's no scenario that a callback - // will subscribe for both configurators and routers - if (key.endsWith(Constants.CONFIGURATORS_SUFFIX)) { - type = ConfigType.CONFIGURATORS; - } else { - /** - * used for all router rules: - * {@link Constants.ROUTERS_SUFFIX} - * {@link org.apache.dubbo.rpc.cluster.router.tag.TagRouter.TAGRULE_DATAID} - */ - type = ConfigType.ROUTERS; - } + } + + public void addListener(ConfigurationListener listener) { + this.listeners.add(listener); } @Override @@ -145,10 +146,9 @@ public class ArchaiusDynamicConfiguration extends AbstractDynamicConfiguration<R DynamicStringProperty prop = DynamicPropertyFactory.getInstance() .getStringProperty(key, null); String newValue = prop.get(); - ConfigChangeEvent event = new ConfigChangeEvent(key, newValue, type); + ConfigChangeEvent event = new ConfigChangeEvent(key, newValue); if (newValue == null) { event.setChangeType(ConfigChangeType.DELETED); - listener.process(event); } else { if (newValue.equals("")) { logger.warn("an empty rule is received for " + key + ", the current working rule is unknown, " + @@ -156,8 +156,8 @@ public class ArchaiusDynamicConfiguration extends AbstractDynamicConfiguration<R return; } event.setChangeType(ConfigChangeType.MODIFIED); - listener.process(event); } + listeners.forEach(listener -> listener.process(event)); } } } diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/sources/ZooKeeperConfigurationSource.java b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/sources/ZooKeeperConfigurationSource.java similarity index 98% rename from dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/sources/ZooKeeperConfigurationSource.java rename to dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/sources/ZooKeeperConfigurationSource.java index c7d5082..e77e434 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/archaius/sources/ZooKeeperConfigurationSource.java +++ b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/sources/ZooKeeperConfigurationSource.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.configcenter.support.archaius.sources; +package org.apache.dubbo.configcenter.support.zookeeper.sources; import com.google.common.io.Closeables; import com.netflix.config.WatchedConfigurationSource; @@ -185,7 +185,7 @@ public class ZooKeeperConfigurationSource implements WatchedConfigurationSource, } }, executor); - // passing true to trigger an initial rebuild upon starting. (blocking call) + // it's not blocking, so we use an extra latch 'initializedLatch' to make sure cache fully initialized before use. treeCache.start(); } diff --git a/dubbo-configcenter/dubbo-configcenter-archaius/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration new file mode 100644 index 0000000..d271f16 --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-archaius/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration @@ -0,0 +1 @@ +archaius=org.apache.dubbo.configcenter.support.archaius.ArchaiusDynamicConfiguration \ No newline at end of file diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/pom.xml similarity index 81% copy from dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml copy to dubbo-configcenter/dubbo-configcenter-zookeeper-cache/pom.xml index 8e3d7fe..191b4ae 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/pom.xml @@ -22,10 +22,10 @@ <artifactId>dubbo-configcenter</artifactId> <version>2.7.0-SNAPSHOT</version> </parent> - <artifactId>dubbo-configcenter-zookeeper</artifactId> + <artifactId>dubbo-configcenter-zookeeper-cache</artifactId> <packaging>jar</packaging> <name>${project.artifactId}</name> - <description>The zookeeper implementation of the configcenter api</description> + <description>The zookeeper implementation of the config-center api</description> <dependencies> <dependency> @@ -34,20 +34,20 @@ <version>${project.parent.version}</version> </dependency> <dependency> - <groupId>com.netflix.archaius</groupId> - <artifactId>archaius-core</artifactId> - </dependency> - <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> + <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> + <version>2.12.0</version> </dependency> <dependency> - <groupId>commons-configuration</groupId> - <artifactId>commons-configuration</artifactId> + <groupId>org.apache.curator</groupId> + <artifactId>curator-test</artifactId> + <version>2.12.0</version> + <scope>test</scope> </dependency> </dependencies> </project> \ No newline at end of file diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/CacheListener.java b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/CacheListener.java new file mode 100644 index 0000000..bbfa057 --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/CacheListener.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.support.zookeeper; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.TreeCacheEvent; +import org.apache.curator.framework.recipes.cache.TreeCacheListener; +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.configcenter.ConfigChangeEvent; +import org.apache.dubbo.configcenter.ConfigChangeType; +import org.apache.dubbo.configcenter.ConfigurationListener; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.CountDownLatch; + +/** + * + */ +public class CacheListener implements TreeCacheListener { + private Map<String, Set<ConfigurationListener>> listeners = new ConcurrentHashMap<>(); + private CountDownLatch initializedLatch; + private String rootPath; + + public CacheListener(String rootPath, CountDownLatch initializedLatch) { + this.rootPath = rootPath; + this.initializedLatch = initializedLatch; + } + + @Override + public void childEvent(CuratorFramework aClient, TreeCacheEvent event) throws Exception { + + TreeCacheEvent.Type type = event.getType(); + ChildData data = event.getData(); + if (type == TreeCacheEvent.Type.INITIALIZED) { + initializedLatch.countDown(); + return; + } + + // TODO, ignore other event types + if (data == null) { + return; + } + + // TODO We limit the notification of config changes to a specific path level, for example + // /dubbo/config/service/configurators, other config changes not in this level will not get notified, + // say /dubbo/config/dubbo.properties + if (data.getPath().split("/").length == 5) { + byte[] value = data.getData(); + String key = pathToKey(data.getPath()); + ConfigChangeType changeType; + switch (type) { + case NODE_ADDED: + changeType = ConfigChangeType.ADDED; + break; + case NODE_REMOVED: + changeType = ConfigChangeType.DELETED; + break; + case NODE_UPDATED: + changeType = ConfigChangeType.MODIFIED; + break; + default: + return; + } + + ConfigChangeEvent configChangeEvent = new ConfigChangeEvent(key, new String(value, StandardCharsets.UTF_8), changeType); + listeners.get(key).forEach(listener -> listener.process(configChangeEvent)); + } + } + + public void addListener(String key, ConfigurationListener configurationListener) { + Set<ConfigurationListener> set = this.listeners.computeIfAbsent(key, k -> new CopyOnWriteArraySet<>()); + set.add(configurationListener); + } + + /** + * This is used to convert a configuration nodePath into a key + * + * @param path + * @return key (nodePath less the config root path) + */ + private String pathToKey(String path) { + if (StringUtils.isEmpty(path)) { + return path; + } + return path.replace(rootPath + "/", "").replaceAll("/", "."); + } +} diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java new file mode 100644 index 0000000..6e78079 --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.support.zookeeper; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.TreeCache; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.dubbo.common.Constants; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.configcenter.AbstractDynamicConfiguration; +import org.apache.dubbo.configcenter.ConfigurationListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.apache.curator.framework.CuratorFrameworkFactory.newClient; +import static org.apache.dubbo.common.Constants.CONFIG_NAMESPACE_KEY; + +/** + * + */ +public class ZookeeperDynamicConfiguration extends AbstractDynamicConfiguration<CacheListener> { + private static final Logger logger = LoggerFactory.getLogger(ZookeeperDynamicConfiguration.class); + private Executor executor = Executors.newFixedThreadPool(1); + private CuratorFramework client; + + // The final root path would be: /configRootPath/"config" + private String rootPath; + private TreeCache treeCache; + private CountDownLatch initializedLatch = new CountDownLatch(1); + + private CacheListener cacheListener; + + @Override + public void initWith(URL url) { + super.initWith(url); + + rootPath = "/" + url.getParameter(CONFIG_NAMESPACE_KEY, Constants.DUBBO) + "/config"; + + RetryPolicy policy = new ExponentialBackoffRetry(1000, 3); + int sessionTimeout = 60 * 1000; + int connectTimeout = 10 * 1000; + String connectString = url.getBackupAddress(); + client = newClient(connectString, sessionTimeout, connectTimeout, policy); + client.start(); + + try { + boolean connected = client.blockUntilConnected(3 * connectTimeout, TimeUnit.MILLISECONDS); + if (!connected) { + if (url.getParameter(Constants.CONFIG_CHECK_KEY, false)) { + throw new IllegalStateException("Failed to connect to config center (zookeeper): " + + connectString + " in " + 3 * connectTimeout + "ms."); + } else { + logger.warn("The config center (zookeeper) is not fully initialized in " + 3 * connectTimeout + "ms, address is: " + connectString); + } + } + } catch (InterruptedException e) { + throw new IllegalStateException("The thread was interrupted unexpectedly when trying connecting to zookeeper " + + connectString + " config center, ", e); + } + + this.cacheListener = new CacheListener(rootPath, initializedLatch); + + // build local cache + try { + this.buildCache(); + } catch (Exception e) { + logger.warn("Failed to build local cache for config center (zookeeper), address is ." + connectString); + } + } + + @Override + protected String getTargetConfig(String key, String group, long timeout) { + if (StringUtils.isNotEmpty(group)) { + key = group + "." + key; + } + + return (String) getInternalProperty(key); + } + + @Override + protected void addConfigurationListener(String key, CacheListener cacheListener, ConfigurationListener listener) { + cacheListener.addListener(key, listener); + } + + @Override + protected CacheListener createTargetListener(String key) { + return cacheListener; + } + + /** + * + * @param key e.g., {service}.configurators, {service}.tagrouters, {group}.dubbo.properties + * @return + */ + @Override + protected Object getInternalProperty(String key) { + ChildData childData = treeCache.getCurrentData(rootPath + "/" + key.replaceFirst("\\.", "/")); + if (childData != null) { + return new String(childData.getData(), StandardCharsets.UTF_8); + } + return null; + } + + protected void recover() { + + } + + /** + * Adds a listener to the pathChildrenCache, initializes the cache, then starts the cache-management background + * thread + */ + private void buildCache() throws Exception { + this.treeCache = new TreeCache(client, rootPath); + // create the watcher for future configuration updates + treeCache.getListenable().addListener(cacheListener, executor); + + // it's not blocking, so we use an extra latch 'initializedLatch' to make sure cache fully initialized before use. + treeCache.start(); + initializedLatch.await(); + } +} diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration new file mode 100644 index 0000000..0379c0e --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration @@ -0,0 +1 @@ +zookeeper=org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfiguration \ No newline at end of file diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java new file mode 100644 index 0000000..382e77d --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper-cache/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.support.zookeeper; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.test.TestingServer; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.configcenter.ConfigChangeEvent; +import org.apache.dubbo.configcenter.ConfigurationListener; +import org.apache.dubbo.configcenter.DynamicConfiguration; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +/** + * + */ +public class ZookeeperDynamicConfigurationTest { + private static CuratorFramework client; + + private static URL configUrl; + private static int zkServerPort = NetUtils.getAvailablePort(); + private static TestingServer zkServer; + private static DynamicConfiguration configuration; + + @BeforeClass + public static void setUp() throws Exception { + zkServer = new TestingServer(zkServerPort, true); + + client = CuratorFrameworkFactory.newClient("localhost:" + zkServerPort, 60 * 1000, 60 * 1000, + new ExponentialBackoffRetry(1000, 3)); + client.start(); + + try { + setData("/dubbo/config/dubbo/dubbo.properties", "The content from dubbo.properties"); + setData("/dubbo/config/group*service:version/configurators", "The content from configurators"); + setData("/dubbo/config/appname", "The content from higer level node"); + setData("/dubbo/config/appname/tagrouters", "The content from appname tagrouters"); + } catch (Exception e) { + e.printStackTrace(); + } + + + configUrl = URL.valueOf("zookeeper://localhost:" + zkServerPort); + + configuration = ExtensionLoader.getExtensionLoader(DynamicConfiguration.class).getExtension(configUrl.getProtocol()); + configuration.initWith(configUrl); + } + + @AfterClass + public static void tearDown() throws Exception { + zkServer.stop(); + } + + @Test + public void testGetConfig() throws Exception { + Assert.assertEquals("The content from dubbo.properties", configuration.getConfig("dubbo.dubbo.properties")); + Assert.assertEquals("The content from dubbo.properties", configuration.getConfig("dubbo.properties", "dubbo")); + } + + @Test + public void testAddListener() throws Exception { + CountDownLatch latch = new CountDownLatch(4); + TestListener listener1 = new TestListener(latch); + TestListener listener2 = new TestListener(latch); + TestListener listener3 = new TestListener(latch); + TestListener listener4 = new TestListener(latch); + configuration.addListener("group*service:version.configurators", listener1); + configuration.addListener("group*service:version.configurators", listener2); + configuration.addListener("appname.tagrouters", listener3); + configuration.addListener("appname.tagrouters", listener4); + + setData("/dubbo/config/group*service:version/configurators", "new value1"); + Thread.sleep(100); + setData("/dubbo/config/appname/tagrouters", "new value2"); + Thread.sleep(100); + setData("/dubbo/config/appname", "new value3"); + + Thread.sleep(5000); + + latch.await(); + Assert.assertEquals(1, listener1.getCount("group*service:version.configurators")); + Assert.assertEquals(1, listener2.getCount("group*service:version.configurators")); + Assert.assertEquals(1, listener3.getCount("appname.tagrouters")); + Assert.assertEquals(1, listener4.getCount("appname.tagrouters")); + + Assert.assertEquals("new value1", listener1.getValue()); + Assert.assertEquals("new value1", listener2.getValue()); + Assert.assertEquals("new value2", listener3.getValue()); + Assert.assertEquals("new value2", listener4.getValue()); + } + + private static void setData(String path, String data) throws Exception { + if (client.checkExists().forPath(path) == null) { + client.create().creatingParentsIfNeeded().forPath(path); + } + client.setData().forPath(path, data.getBytes()); + } + + private class TestListener implements ConfigurationListener { + private CountDownLatch latch; + private String value; + private Map<String, Integer> countMap = new HashMap<>(); + + public TestListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void process(ConfigChangeEvent event) { + Integer count = countMap.computeIfAbsent(event.getKey(), k -> new Integer(0)); + countMap.put(event.getKey(), ++count); + + value = event.getNewValue(); + latch.countDown(); + } + + public int getCount(String key) { + return countMap.get(key); + } + + public String getValue() { + return value; + } + } + +} diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml b/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml index 8e3d7fe..717f5ef 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/pom.xml @@ -25,7 +25,7 @@ <artifactId>dubbo-configcenter-zookeeper</artifactId> <packaging>jar</packaging> <name>${project.artifactId}</name> - <description>The zookeeper implementation of the configcenter api</description> + <description>The zookeeper implementation of the config-center api</description> <dependencies> <dependency> @@ -33,21 +33,31 @@ <artifactId>dubbo-configcenter-api</artifactId> <version>${project.parent.version}</version> </dependency> - <dependency> - <groupId>com.netflix.archaius</groupId> - <artifactId>archaius-core</artifactId> - </dependency> + <!--<dependency>--> + <!--<groupId>org.apache.curator</groupId>--> + <!--<artifactId>curator-framework</artifactId>--> + <!--</dependency>--> <dependency> <groupId>org.apache.curator</groupId> - <artifactId>curator-framework</artifactId> + <artifactId>curator-recipes</artifactId> </dependency> <dependency> <groupId>org.apache.curator</groupId> - <artifactId>curator-recipes</artifactId> + <artifactId>curator-x-async</artifactId> + <version>4.0.1</version> + <exclusions> + <exclusion> + <groupId>org.apache.zookeeper</groupId> + <artifactId>zookeeper</artifactId> + </exclusion> + </exclusions> </dependency> + <dependency> - <groupId>commons-configuration</groupId> - <artifactId>commons-configuration</artifactId> + <groupId>org.apache.curator</groupId> + <artifactId>curator-test</artifactId> + <version>2.12.0</version> + <scope>test</scope> </dependency> </dependencies> -</project> \ No newline at end of file +</project> diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration index b948be5..0379c0e 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfiguration @@ -1,2 +1 @@ -archaius=org.apache.dubbo.configcenter.support.archaius.ArchaiusDynamicConfiguration -zookeeper=org.apache.dubbo.configcenter.support.archaius.ArchaiusDynamicConfiguration \ No newline at end of file +zookeeper=org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfiguration \ No newline at end of file diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java new file mode 100644 index 0000000..382e77d --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.support.zookeeper; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.test.TestingServer; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.configcenter.ConfigChangeEvent; +import org.apache.dubbo.configcenter.ConfigurationListener; +import org.apache.dubbo.configcenter.DynamicConfiguration; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +/** + * + */ +public class ZookeeperDynamicConfigurationTest { + private static CuratorFramework client; + + private static URL configUrl; + private static int zkServerPort = NetUtils.getAvailablePort(); + private static TestingServer zkServer; + private static DynamicConfiguration configuration; + + @BeforeClass + public static void setUp() throws Exception { + zkServer = new TestingServer(zkServerPort, true); + + client = CuratorFrameworkFactory.newClient("localhost:" + zkServerPort, 60 * 1000, 60 * 1000, + new ExponentialBackoffRetry(1000, 3)); + client.start(); + + try { + setData("/dubbo/config/dubbo/dubbo.properties", "The content from dubbo.properties"); + setData("/dubbo/config/group*service:version/configurators", "The content from configurators"); + setData("/dubbo/config/appname", "The content from higer level node"); + setData("/dubbo/config/appname/tagrouters", "The content from appname tagrouters"); + } catch (Exception e) { + e.printStackTrace(); + } + + + configUrl = URL.valueOf("zookeeper://localhost:" + zkServerPort); + + configuration = ExtensionLoader.getExtensionLoader(DynamicConfiguration.class).getExtension(configUrl.getProtocol()); + configuration.initWith(configUrl); + } + + @AfterClass + public static void tearDown() throws Exception { + zkServer.stop(); + } + + @Test + public void testGetConfig() throws Exception { + Assert.assertEquals("The content from dubbo.properties", configuration.getConfig("dubbo.dubbo.properties")); + Assert.assertEquals("The content from dubbo.properties", configuration.getConfig("dubbo.properties", "dubbo")); + } + + @Test + public void testAddListener() throws Exception { + CountDownLatch latch = new CountDownLatch(4); + TestListener listener1 = new TestListener(latch); + TestListener listener2 = new TestListener(latch); + TestListener listener3 = new TestListener(latch); + TestListener listener4 = new TestListener(latch); + configuration.addListener("group*service:version.configurators", listener1); + configuration.addListener("group*service:version.configurators", listener2); + configuration.addListener("appname.tagrouters", listener3); + configuration.addListener("appname.tagrouters", listener4); + + setData("/dubbo/config/group*service:version/configurators", "new value1"); + Thread.sleep(100); + setData("/dubbo/config/appname/tagrouters", "new value2"); + Thread.sleep(100); + setData("/dubbo/config/appname", "new value3"); + + Thread.sleep(5000); + + latch.await(); + Assert.assertEquals(1, listener1.getCount("group*service:version.configurators")); + Assert.assertEquals(1, listener2.getCount("group*service:version.configurators")); + Assert.assertEquals(1, listener3.getCount("appname.tagrouters")); + Assert.assertEquals(1, listener4.getCount("appname.tagrouters")); + + Assert.assertEquals("new value1", listener1.getValue()); + Assert.assertEquals("new value1", listener2.getValue()); + Assert.assertEquals("new value2", listener3.getValue()); + Assert.assertEquals("new value2", listener4.getValue()); + } + + private static void setData(String path, String data) throws Exception { + if (client.checkExists().forPath(path) == null) { + client.create().creatingParentsIfNeeded().forPath(path); + } + client.setData().forPath(path, data.getBytes()); + } + + private class TestListener implements ConfigurationListener { + private CountDownLatch latch; + private String value; + private Map<String, Integer> countMap = new HashMap<>(); + + public TestListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void process(ConfigChangeEvent event) { + Integer count = countMap.computeIfAbsent(event.getKey(), k -> new Integer(0)); + countMap.put(event.getKey(), ++count); + + value = event.getNewValue(); + latch.countDown(); + } + + public int getCount(String key) { + return countMap.get(key); + } + + public String getValue() { + return value; + } + } + +} diff --git a/dubbo-configcenter/pom.xml b/dubbo-configcenter/pom.xml index fa1be8e..4d7dd84 100644 --- a/dubbo-configcenter/pom.xml +++ b/dubbo-configcenter/pom.xml @@ -25,14 +25,16 @@ <artifactId>dubbo-configcenter</artifactId> <packaging>pom</packaging> <name>${project.artifactId}</name> - <description>The service configcenter module of the Dubbo project</description> + <description>The service config-center module of the Dubbo project</description> <properties> <skip_maven_deploy>false</skip_maven_deploy> </properties> <modules> <module>dubbo-configcenter-api</module> - <module>dubbo-configcenter-apollo</module> <module>dubbo-configcenter-zookeeper</module> + <!--<module>dubbo-configcenter-zookeeper-cache</module>--> + <module>dubbo-configcenter-apollo</module> + <module>dubbo-configcenter-archaius</module> </modules> </project> \ No newline at end of file