You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/07/16 02:18:10 UTC
[dubbo] branch 3.0 updated: [3.0] Lock-free ConfigManager and
improve config checking (#8289)
This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/3.0 by this push:
new a5737d1 [3.0] Lock-free ConfigManager and improve config checking (#8289)
a5737d1 is described below
commit a5737d1625259837cff4555b94ac92301eb56642
Author: Gong Dewei <ky...@qq.com>
AuthorDate: Fri Jul 16 10:17:58 2021 +0800
[3.0] Lock-free ConfigManager and improve config checking (#8289)
* Improve config equals() and toString() performance
* Lock-free ConfigManager through ConcurrentHashMap, improve reference config checking
---
.../org/apache/dubbo/config/AbstractConfig.java | 143 +++++++--------
.../dubbo/config/AbstractInterfaceConfig.java | 15 +-
.../apache/dubbo/config/context/ConfigManager.java | 197 ++++++++-------------
.../apache/dubbo/config/ReferenceConfigTest.java | 92 ++++++++--
4 files changed, 230 insertions(+), 217 deletions(-)
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
index 5b8c4e1..efe5941 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
@@ -67,9 +67,14 @@ public abstract class AbstractConfig implements Serializable {
private static final long serialVersionUID = 4267533505537413570L;
/**
- * The field names cache of config class
+ * tag name cache, speed up get tag name frequently
*/
- private static final Map<Class, Set<String>> fieldNamesCache = new ConcurrentHashMap<>();
+ private static final Map<Class, String> tagNameCache = new ConcurrentHashMap<>();
+
+ /**
+ * attributed getter method cache for equals(), hashCode() and toString()
+ */
+ private static final Map<Class, List<Method>> attributedMethodCache = new ConcurrentHashMap<>();
/**
* The suffix container
@@ -90,14 +95,16 @@ public abstract class AbstractConfig implements Serializable {
public static String getTagName(Class<?> cls) {
- String tag = cls.getSimpleName();
- for (String suffix : SUFFIXES) {
- if (tag.endsWith(suffix)) {
- tag = tag.substring(0, tag.length() - suffix.length());
- break;
+ return tagNameCache.computeIfAbsent(cls, (key)-> {
+ String tag = cls.getSimpleName();
+ for (String suffix : SUFFIXES) {
+ if (tag.endsWith(suffix)) {
+ tag = tag.substring(0, tag.length() - suffix.length());
+ break;
+ }
}
- }
- return StringUtils.camelToSplitName(tag, "-");
+ return StringUtils.camelToSplitName(tag, "-");
+ });
}
public static String getPluralTagName(Class<?> cls) {
@@ -134,7 +141,7 @@ public abstract class AbstractConfig implements Serializable {
if (config == null) {
return;
}
- // If asParameters=false, it means as attributes, ignore @Parameter annotation except 'append' and 'attribute'
+ // If asParameters=false, it means append attributes, ignore @Parameter annotation's attributes except 'append' and 'attribute'
// How to select the appropriate one from multiple getter methods of the property?
// e.g. Using String getGeneric() or Boolean isGeneric()? Judge by field type ?
@@ -426,7 +433,7 @@ public abstract class AbstractConfig implements Serializable {
return metaData;
}
- protected static BeanInfo getBeanInfo(Class cls) {
+ private static BeanInfo getBeanInfo(Class cls) {
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(cls);
@@ -641,29 +648,14 @@ public abstract class AbstractConfig implements Serializable {
public String toString() {
try {
- Set<String> fieldNames = getFieldNames(this.getClass());
-
StringBuilder buf = new StringBuilder();
buf.append("<dubbo:");
buf.append(getTagName(getClass()));
- Method[] methods = getClass().getMethods();
- for (Method method : methods) {
+ for (Method method : getAttributedMethods()) {
try {
if (MethodUtils.isGetter(method)) {
String name = method.getName();
String key = calculateAttributeFromGetter(name);
-
- // Fixes #4992, endless recursive call when NetUtils method fails.
- if (!fieldNames.contains(key)) {
- continue;
- }
-
- // filter non attribute
- Parameter parameter = method.getAnnotation(Parameter.class);
- if (parameter != null && !parameter.attribute()) {
- continue;
- }
-
Object value = method.invoke(this);
if (value != null) {
buf.append(' ');
@@ -685,10 +677,6 @@ public abstract class AbstractConfig implements Serializable {
}
}
- private static Set<String> getFieldNames(Class<?> configClass) {
- return fieldNamesCache.computeIfAbsent(configClass, ReflectUtils::getAllFieldNames);
- }
-
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != this.getClass()) {
@@ -698,33 +686,19 @@ public abstract class AbstractConfig implements Serializable {
return true;
}
- BeanInfo beanInfo = getBeanInfo(this.getClass());
- for (MethodDescriptor methodDescriptor : beanInfo.getMethodDescriptors()) {
- Method method = methodDescriptor.getMethod();
- if (MethodUtils.isGetter(method)) {
- // filter non attribute
- Parameter parameter = method.getAnnotation(Parameter.class);
- if (parameter != null && !parameter.attribute()) {
- continue;
- }
- String propertyName = calculateAttributeFromGetter(method.getName());
- // ignore compare 'id' value
- if (Constants.ID.equals(propertyName)) {
- continue;
- }
- // filter non writable property
- if (!isWritableProperty(beanInfo, propertyName)) {
- continue;
- }
- try {
- Object value1 = method.invoke(this);
- Object value2 = method.invoke(obj);
- if (!Objects.equals(value1, value2)) {
- return false;
- }
- } catch (Exception e) {
- throw new IllegalStateException("compare config instances failed", e);
+ for (Method method : getAttributedMethods()) {
+ // ignore compare 'id' value
+ if ("getId".equals(method.getName())) {
+ continue;
+ }
+ try {
+ Object value1 = method.invoke(this);
+ Object value2 = method.invoke(obj);
+ if (!Objects.equals(value1, value2)) {
+ return false;
}
+ } catch (Exception e) {
+ throw new IllegalStateException("compare config instances failed", e);
}
}
return true;
@@ -734,28 +708,57 @@ public abstract class AbstractConfig implements Serializable {
public int hashCode() {
int hashCode = 1;
- Method[] methods = this.getClass().getMethods();
- for (Method method : methods) {
- if (MethodUtils.isGetter(method)) {
- Parameter parameter = method.getAnnotation(Parameter.class);
- // filter non attribute
- if (parameter != null && !parameter.attribute()) {
- continue;
- }
- try {
- Object value = method.invoke(this);
+ for (Method method : getAttributedMethods()) {
+ // ignore compare 'id' value
+ if ("getId".equals(method.getName())) {
+ continue;
+ }
+ try {
+ Object value = method.invoke(this);
+ if (value != null) {
hashCode = 31 * hashCode + value.hashCode();
- } catch (Exception ignored) {
- //ignored
}
+ } catch (Exception ignored) {
+ //ignored
}
}
if (hashCode == 0) {
hashCode = 1;
}
-
return hashCode;
}
+ private List<Method> getAttributedMethods() {
+ Class<? extends AbstractConfig> cls = this.getClass();
+ return attributedMethodCache.computeIfAbsent(cls, (key)-> computeAttributedMethods());
+ }
+
+ /**
+ * compute attributed getter methods, subclass can override this method to add/remove attributed methods
+ * @return
+ */
+ protected List<Method> computeAttributedMethods() {
+ Class<? extends AbstractConfig> cls = this.getClass();
+ BeanInfo beanInfo = getBeanInfo(cls);
+ List<Method> methods = new ArrayList<>(beanInfo.getMethodDescriptors().length);
+ for (MethodDescriptor methodDescriptor : beanInfo.getMethodDescriptors()) {
+ Method method = methodDescriptor.getMethod();
+ if (MethodUtils.isGetter(method)) {
+ // filter non attribute
+ Parameter parameter = method.getAnnotation(Parameter.class);
+ if (parameter != null && !parameter.attribute()) {
+ continue;
+ }
+ String propertyName = calculateAttributeFromGetter(method.getName());
+ // filter non writable property, exclude non property methods, fix #4225
+ if (!isWritableProperty(beanInfo, propertyName)) {
+ continue;
+ }
+ methods.add(method);
+ }
+ }
+ return methods;
+ }
+
}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
index bc4f177..c8dd6c1 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
@@ -25,7 +25,6 @@ import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.config.context.ConfigManager;
import org.apache.dubbo.config.support.Parameter;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ServiceMetadata;
@@ -661,12 +660,7 @@ public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
public void setConfigCenter(ConfigCenterConfig configCenter) {
this.configCenter = configCenter;
if (configCenter != null) {
- ConfigManager configManager = ApplicationModel.getConfigManager();
- Collection<ConfigCenterConfig> configs = configManager.getConfigCenters();
- if (CollectionUtils.isEmpty(configs)
- || configs.stream().noneMatch(existed -> existed.equals(configCenter))) {
- configManager.addConfigCenter(configCenter);
- }
+ ApplicationModel.getConfigManager().addConfigCenter(configCenter);
}
}
@@ -718,12 +712,7 @@ public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
public void setMetadataReportConfig(MetadataReportConfig metadataReportConfig) {
this.metadataReportConfig = metadataReportConfig;
if (metadataReportConfig != null) {
- ConfigManager configManager = ApplicationModel.getConfigManager();
- Collection<MetadataReportConfig> configs = configManager.getMetadataConfigs();
- if (CollectionUtils.isEmpty(configs)
- || configs.stream().noneMatch(existed -> existed.equals(metadataReportConfig))) {
- configManager.addMetadataReport(metadataReportConfig);
- }
+ ApplicationModel.getConfigManager().addMetadataReport(metadataReportConfig);
}
}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
index 56d72a3..36d8ed4 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
@@ -48,19 +48,12 @@ import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static java.lang.Boolean.TRUE;
@@ -69,6 +62,10 @@ import static java.util.Optional.ofNullable;
import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
import static org.apache.dubbo.config.AbstractConfig.getTagName;
+/**
+ * A lock-free config manager (through ConcurrentHashMap), for fast read operation.
+ * The Write operation lock with sub configs map of config type, for safely check and add new config.
+ */
public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
@@ -78,15 +75,13 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
private static final String CONFIG_NAME_READ_METHOD = "getName";
public static final String DUBBO_CONFIG_MODE = ConfigKeys.DUBBO_CONFIG_MODE;
- private final ReadWriteLock lock = new ReentrantReadWriteLock();
-
- final Map<String, Map<String, AbstractConfig>> configsCache = newMap();
+ final Map<String, Map<String, AbstractConfig>> configsCache = new ConcurrentHashMap<>();
- private Map<String, AbstractInterfaceConfig> referenceConfigCache = new HashMap<>();
+ private Map<String, AbstractInterfaceConfig> referenceConfigCache = new ConcurrentHashMap<>();
- private Map<String, AbstractInterfaceConfig> serviceConfigCache = new HashMap<>();
+ private Map<String, AbstractInterfaceConfig> serviceConfigCache = new ConcurrentHashMap<>();
- private Set<AbstractConfig> duplicatedConfigs = new HashSet<>();
+ private Set<AbstractConfig> duplicatedConfigs = new ConcurrentHashSet<>();
private ConfigMode configMode = ConfigMode.STRICT;
@@ -407,22 +402,19 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
public void refreshAll() {
- write(() -> {
- // refresh all configs here,
- getApplication().ifPresent(ApplicationConfig::refresh);
- getMonitor().ifPresent(MonitorConfig::refresh);
- getModule().ifPresent(ModuleConfig::refresh);
- getMetrics().ifPresent(MetricsConfig::refresh);
- getSsl().ifPresent(SslConfig::refresh);
-
- getProtocols().forEach(ProtocolConfig::refresh);
- getRegistries().forEach(RegistryConfig::refresh);
- getProviders().forEach(ProviderConfig::refresh);
- getConsumers().forEach(ConsumerConfig::refresh);
- getConfigCenters().forEach(ConfigCenterConfig::refresh);
- getMetadataConfigs().forEach(MetadataReportConfig::refresh);
- });
+ // refresh all configs here,
+ getApplication().ifPresent(ApplicationConfig::refresh);
+ getMonitor().ifPresent(MonitorConfig::refresh);
+ getModule().ifPresent(ModuleConfig::refresh);
+ getMetrics().ifPresent(MetricsConfig::refresh);
+ getSsl().ifPresent(SslConfig::refresh);
+ getProtocols().forEach(ProtocolConfig::refresh);
+ getRegistries().forEach(RegistryConfig::refresh);
+ getProviders().forEach(ProviderConfig::refresh);
+ getConsumers().forEach(ConsumerConfig::refresh);
+ getConfigCenters().forEach(ConfigCenterConfig::refresh);
+ getMetadataConfigs().forEach(MetadataReportConfig::refresh);
}
/**
@@ -442,13 +434,11 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
public void clear() {
- write(() -> {
- this.configsCache.clear();
- configIdIndexes.clear();
- this.referenceConfigCache.clear();
- this.serviceConfigCache.clear();
- this.duplicatedConfigs.clear();
- });
+ this.configsCache.clear();
+ configIdIndexes.clear();
+ this.referenceConfigCache.clear();
+ this.serviceConfigCache.clear();
+ this.duplicatedConfigs.clear();
}
/**
@@ -484,10 +474,22 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
if (config instanceof MethodConfig) {
return null;
}
- return (T) write(() -> {
- Map<String, AbstractConfig> configsMap = configsCache.computeIfAbsent(getTagName(config.getClass()), type -> newMap());
- return addIfAbsent(config, configsMap, unique);
- });
+
+ Map<String, AbstractConfig> configsMap = configsCache.computeIfAbsent(getTagName(config.getClass()), type -> newMap());
+
+ // fast check duplicated equivalent config before write lock
+ if (!(config instanceof ReferenceConfigBase || config instanceof ServiceConfigBase)) {
+ for (AbstractConfig value : configsMap.values()) {
+ if (value.equals(config)) {
+ return (T) value;
+ }
+ }
+ }
+
+ // lock by config type
+ synchronized (configsMap) {
+ return (T) addIfAbsent(config, configsMap, unique);
+ }
}
public <C extends AbstractConfig> Map<String, C> getConfigsMap(Class<C> cls) {
@@ -495,15 +497,15 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
private <C extends AbstractConfig> Map<String, C> getConfigsMap(String configType) {
- return (Map<String, C>) read(() -> configsCache.getOrDefault(configType, emptyMap()));
+ return (Map<String, C>) configsCache.getOrDefault(configType, emptyMap());
}
private <C extends AbstractConfig> Collection<C> getConfigs(String configType) {
- return (Collection<C>) read(() -> getConfigsMap(configType).values());
+ return (Collection<C>) getConfigsMap(configType).values();
}
public <C extends AbstractConfig> Collection<C> getConfigs(Class<C> configType) {
- return (Collection<C>) read(() -> getConfigsMap(getTagName(configType)).values());
+ return (Collection<C>) getConfigsMap(getTagName(configType)).values();
}
/**
@@ -513,10 +515,7 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
* @return
*/
private <C extends AbstractConfig> C getConfigById(String configType, String id) {
- return read(() -> {
- Map<String, C> configsMap = (Map) configsCache.getOrDefault(configType, emptyMap());
- return configsMap.get(id);
- });
+ return (C) getConfigsMap(configType).get(id);
}
/**
@@ -526,26 +525,23 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
* @return
*/
private <C extends AbstractConfig> C getConfigByName(Class<? extends C> cls, String name) {
- return read(() -> {
- String configType = getTagName(cls);
- Map<String, C> configsMap = (Map) configsCache.getOrDefault(configType, emptyMap());
- if (configsMap.isEmpty()) {
- return null;
- }
- // try find config by name
- if (ReflectUtils.hasMethod(cls, CONFIG_NAME_READ_METHOD)) {
- List<C> list = configsMap.values().stream()
- .filter(cfg -> name.equals(getConfigName(cfg)))
- .collect(Collectors.toList());
- if (list.size() > 1) {
- throw new IllegalStateException("Found more than one config by name: " + name +
- ", instances: " + list + ". Please remove redundant configs or get config by id.");
- } else if (list.size() == 1) {
- return list.get(0);
- }
- }
+ Map<String, ? extends C> configsMap = getConfigsMap(cls);
+ if (configsMap.isEmpty()) {
return null;
- });
+ }
+ // try find config by name
+ if (ReflectUtils.hasMethod(cls, CONFIG_NAME_READ_METHOD)) {
+ List<C> list = configsMap.values().stream()
+ .filter(cfg -> name.equals(getConfigName(cfg)))
+ .collect(Collectors.toList());
+ if (list.size() > 1) {
+ throw new IllegalStateException("Found more than one config by name: " + name +
+ ", instances: " + list + ". Please remove redundant configs or get config by id.");
+ } else if (list.size() == 1) {
+ return list.get(0);
+ }
+ }
+ return null;
}
private <C extends AbstractConfig> String getConfigName(C config) {
@@ -557,56 +553,16 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
protected <C extends AbstractConfig> C getSingleConfig(String configType) throws IllegalStateException {
- return read(() -> {
- Map<String, C> configsMap = (Map) configsCache.getOrDefault(configType, emptyMap());
- int size = configsMap.size();
- if (size < 1) {
+ Map<String, AbstractConfig> configsMap = getConfigsMap(configType);
+ int size = configsMap.size();
+ if (size < 1) {
// throw new IllegalStateException("No such " + configType.getName() + " is found");
- return null;
- } else if (size > 1) {
- throw new IllegalStateException("Expected single instance of " + configType + ", but found " + size +
- " instances, please remove redundant configs. instances: "+configsMap.values());
- }
-
- return configsMap.values().iterator().next();
- });
- }
-
- private <V> V write(Callable<V> callable) {
- V value = null;
- Lock writeLock = lock.writeLock();
- try {
- writeLock.lock();
- value = callable.call();
- } catch (RuntimeException e) {
- throw e;
- } catch (Throwable e) {
- throw new RuntimeException(e.getCause());
- } finally {
- writeLock.unlock();
- }
- return value;
- }
-
- private void write(Runnable runnable) {
- write(() -> {
- runnable.run();
return null;
- });
- }
-
- private <V> V read(Callable<V> callable) {
- Lock readLock = lock.readLock();
- V value = null;
- try {
- readLock.lock();
- value = callable.call();
- } catch (Throwable e) {
- throw new RuntimeException(e);
- } finally {
- readLock.unlock();
+ } else if (size > 1) {
+ throw new IllegalStateException("Expected single instance of " + configType + ", but found " + size +
+ " instances, please remove redundant configs. instances: "+configsMap.values());
}
- return value;
+ return (C) configsMap.values().iterator().next();
}
private static boolean isEquals(AbstractConfig oldOne, AbstractConfig newOne) {
@@ -632,7 +588,7 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
private static Map newMap() {
- return new LinkedHashMap<>();
+ return new ConcurrentHashMap();
}
/**
@@ -651,7 +607,6 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
}
// check duplicated configs
- // TODO Is there any problem with ignoring duplicate and equivalent but different ReferenceConfig instances?
// special check service and reference config by unique service name, speed up the processing of large number of instances
if (config instanceof ReferenceConfigBase || config instanceof ServiceConfigBase) {
C existedConfig = (C) checkDuplicatedInterfaceConfig((AbstractInterfaceConfig) config);
@@ -712,14 +667,11 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
key = generateConfigId(config);
}
- C existedConfig = configsMap.get(key);
-
+ C existedConfig = configsMap.putIfAbsent(key, config);
if (isEquals(existedConfig, config)) {
String type = config.getClass().getSimpleName();
throw new IllegalStateException(String.format("Duplicate %s found, there already has one default %s or more than two %ss have the same id, " +
"you can try to give each %s a different id, key: %s, prev: %s, new: %s", type, type, type, type, key, existedConfig, config));
- } else {
- configsMap.put(key, config);
}
return config;
}
@@ -760,7 +712,12 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
AbstractInterfaceConfig prevConfig = configCache.putIfAbsent(uniqueServiceName, config);
if (prevConfig != null) {
if (prevConfig == config) {
- return config;
+ return prevConfig;
+ }
+
+ if (prevConfig.equals(config)) {
+ // TODO Is there any problem with ignoring duplicate and equivalent but different ReferenceConfig instances?
+ return prevConfig;
}
String configType = config.getClass().getSimpleName();
@@ -781,7 +738,7 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
public static <C extends AbstractConfig> String generateConfigId(C config) {
int idx = configIdIndexes.computeIfAbsent(config.getClass(), clazz -> new AtomicInteger(0)).incrementAndGet();
- return config.getClass().getSimpleName() + "#" + idx;
+ return getTagName(config.getClass()) + "#" + idx;
}
static <C extends AbstractConfig> String getId(C config) {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
index 9b3c0b1..e7c0618 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
@@ -33,9 +33,13 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.IOException;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
@@ -51,6 +55,11 @@ public class ReferenceConfigTest {
this.zkServer = new TestingServer(zkServerPort, true);
this.zkServer.start();
this.registryUrl = "zookeeper://localhost:" + zkServerPort;
+
+ // preload
+ ReferenceConfig preloadReferenceConfig = new ReferenceConfig();
+ ApplicationModel.getConfigManager();
+ DubboBootstrap.getInstance();
}
@AfterEach
@@ -193,25 +202,80 @@ public class ReferenceConfigTest {
}
@Test
- public void testLargeReferences() {
- int amount = 5000;
- List<ReferenceConfig> referenceConfigs = new ArrayList<>(amount);
- for (int i = 0; i < amount; i++) {
- ReferenceConfig referenceConfig = new ReferenceConfig();
- referenceConfig.setInterface("com.test.TestService" + i);
- referenceConfigs.add(referenceConfig);
- }
+ public void testLargeReferences() throws InterruptedException {
+ int amount = 10000;
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("test-app");
+ MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
+ metadataReportConfig.setAddress("metadata://");
+ ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
+ configCenterConfig.setAddress("diamond://");
+
+ testInitReferences(0, amount, applicationConfig, metadataReportConfig, configCenterConfig);
+ ApplicationModel.getConfigManager().clear();
+ testInitReferences(0, 1, applicationConfig, metadataReportConfig, configCenterConfig);
- // test add large number of references
long t1 = System.currentTimeMillis();
- for (ReferenceConfig referenceConfig : referenceConfigs) {
- DubboBootstrap.getInstance().reference(referenceConfig);
+ int nThreads = 8;
+ ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
+ for(int i=0;i<nThreads;i++) {
+ int perCount = (int) (1.0* amount / nThreads);
+ int start = perCount * i;
+ int end = start + perCount;
+ if (i == nThreads - 1) {
+ end = amount;
+ }
+ int finalEnd = end;
+ System.out.println(String.format("start thread %s: range: %s - %s, count: %s", i, start, end, (end-start)));
+ executorService.submit(()->{
+ testInitReferences(start, finalEnd, applicationConfig, metadataReportConfig, configCenterConfig);
+ });
}
+ executorService.shutdown();
+ executorService.awaitTermination(100, TimeUnit.SECONDS);
+
long t2 = System.currentTimeMillis();
long cost = t2 - t1;
- System.out.println("Add large references cost: " + cost + "ms");
+ System.out.println("Init large references cost: " + cost + "ms");
Assertions.assertEquals(amount, DubboBootstrap.getInstance().getConfigManager().getReferences().size());
- Assertions.assertTrue( cost < 500, "add large reference too slowly: "+cost);
+ Assertions.assertTrue( cost < 1000, "Init large references too slowly: "+cost);
+
+ //test equals
+ testSearchReferences();
+
+ }
+
+ private void testSearchReferences() {
+ long t1 = System.currentTimeMillis();
+ Collection<ReferenceConfigBase<?>> references = DubboBootstrap.getInstance().getConfigManager().getReferences();
+ List<ReferenceConfigBase<?>> results = references.stream().filter(rc -> rc.equals(references.iterator().next()))
+ .collect(Collectors.toList());
+ long t2 = System.currentTimeMillis();
+ long cost = t2 - t1;
+ System.out.println("Search large references cost: " + cost + "ms");
+ Assertions.assertEquals(1, results.size());
+ Assertions.assertTrue( cost < 1000, "Search large references too slowly: "+cost);
+ }
+
+ private long testInitReferences(int start, int end, ApplicationConfig applicationConfig, MetadataReportConfig metadataReportConfig, ConfigCenterConfig configCenterConfig) {
+ // test add large number of references
+ long t1 = System.currentTimeMillis();
+ try {
+ for (int i = start; i < end; i++) {
+ ReferenceConfig referenceConfig = new ReferenceConfig();
+ referenceConfig.setInterface("com.test.TestService" + i);
+ referenceConfig.setApplication(applicationConfig);
+ referenceConfig.setMetadataReportConfig(metadataReportConfig);
+ referenceConfig.setConfigCenter(configCenterConfig);
+ DubboBootstrap.getInstance().reference(referenceConfig);
+
+ //ApplicationModel.getConfigManager().getConfigCenters();
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ long t2 = System.currentTimeMillis();
+ return t2 - t1;
}
@Test