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 2024/07/29 02:23:49 UTC
(dubbo-spi-extensions) branch 3.3 updated: Import metadata-report-redis implementation from Dubbo 3.3 (#457)
This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch 3.3
in repository https://gitbox.apache.org/repos/asf/dubbo-spi-extensions.git
The following commit(s) were added to refs/heads/3.3 by this push:
new 13244130 Import metadata-report-redis implementation from Dubbo 3.3 (#457)
13244130 is described below
commit 1324413013e5621b122f332dcc8a85b333539efb
Author: aofall <10...@users.noreply.github.com>
AuthorDate: Mon Jul 29 10:23:44 2024 +0800
Import metadata-report-redis implementation from Dubbo 3.3 (#457)
* Import metadata-report-redis implementation from Dubbo 3.3
* Import metadata-report-redis implementation from Dubbo 3.3
* rename password template
---
.../dubbo-metadata-report-redis/pom.xml | 58 +++
.../metadata/store/redis/RedisMetadataReport.java | 555 +++++++++++++++++++++
.../store/redis/RedisMetadataReportFactory.java | 32 ++
...che.dubbo.metadata.report.MetadataReportFactory | 1 +
.../store/redis/RedisMetadata4TstService.java | 24 +
.../store/redis/RedisMetadataReportTest.java | 365 ++++++++++++++
.../src/test/resources/log4j2-test.xml | 28 ++
dubbo-metadata-report-extensions/pom.xml | 1 +
8 files changed, 1064 insertions(+)
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/pom.xml b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/pom.xml
new file mode 100644
index 00000000..b2cd88cf
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/pom.xml
@@ -0,0 +1,58 @@
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.dubbo.extensions</groupId>
+ <artifactId>dubbo-metadata-report-extensions</artifactId>
+ <version>${revision}</version>
+ </parent>
+
+ <artifactId>dubbo-metadata-report-redis</artifactId>
+
+ <properties>
+ <jedis.version>5.1.0</jedis.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-metadata-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>redis.clients</groupId>
+ <artifactId>jedis</artifactId>
+ <version>${jedis.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.codemonstur</groupId>
+ <artifactId>embedded-redis</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
new file mode 100644
index 00000000..1c010f07
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
@@ -0,0 +1,555 @@
+/*
+ * 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.metadata.store.redis;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.common.utils.JsonUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.MappingChangedEvent;
+import org.apache.dubbo.metadata.MappingListener;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.ServiceNameMapping;
+import org.apache.dubbo.metadata.report.identifier.BaseMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
+import org.apache.dubbo.rpc.RpcException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.JedisPubSub;
+import redis.clients.jedis.Transaction;
+import redis.clients.jedis.params.SetParams;
+import redis.clients.jedis.util.JedisClusterCRC16;
+
+import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.CYCLE_REPORT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.QUEUES_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RESPONSE;
+import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
+import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
+import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
+import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
+
+/**
+ * RedisMetadataReport
+ */
+public class RedisMetadataReport extends AbstractMetadataReport {
+
+ private static final int ONE_DAY_IN_MILLISECONDS = 86400000;
+
+ private static final String REDIS_DATABASE_KEY = "database";
+ private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RedisMetadataReport.class);
+
+ // protected , for test
+ protected JedisPool pool;
+ private Set<HostAndPort> jedisClusterNodes;
+ private int timeout;
+ private String password;
+ private final String root;
+ private final ConcurrentHashMap<String, MappingDataListener> mappingDataListenerMap = new ConcurrentHashMap<>();
+ private SetParams jedisParams = SetParams.setParams();
+
+ public RedisMetadataReport(URL url) {
+ super(url);
+ timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
+ password = url.getPassword();
+ this.root = url.getGroup(DEFAULT_ROOT);
+ if (url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
+ // ttl default is twice the cycle-report time
+ jedisParams.ex(ONE_DAY_IN_MILLISECONDS * 2);
+ }
+ if (url.getParameter(CLUSTER_KEY, false)) {
+ jedisClusterNodes = new HashSet<>();
+ List<URL> urls = url.getBackupUrls();
+ for (URL tmpUrl : urls) {
+ jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
+ }
+ } else {
+ int database = url.getParameter(REDIS_DATABASE_KEY, 0);
+ pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, password, database);
+ }
+ }
+
+ @Override
+ protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
+ this.storeMetadata(providerMetadataIdentifier, serviceDefinitions);
+ }
+
+ @Override
+ protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) {
+ this.storeMetadata(consumerMetadataIdentifier, value);
+ }
+
+ @Override
+ protected void doSaveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier, URL url) {
+ this.storeMetadata(serviceMetadataIdentifier, URL.encode(url.toFullString()));
+ }
+
+ @Override
+ protected void doRemoveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier) {
+ this.deleteMetadata(serviceMetadataIdentifier);
+ }
+
+ @Override
+ protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+ String content = getMetadata(metadataIdentifier);
+ if (StringUtils.isEmpty(content)) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(Arrays.asList(URL.decode(content)));
+ }
+
+ @Override
+ protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
+ this.storeMetadata(subscriberMetadataIdentifier, urlListStr);
+ }
+
+ @Override
+ protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+ return this.getMetadata(subscriberMetadataIdentifier);
+ }
+
+ @Override
+ public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
+ return this.getMetadata(metadataIdentifier);
+ }
+
+ private void storeMetadata(BaseMetadataIdentifier metadataIdentifier, String v) {
+ if (pool != null) {
+ storeMetadataStandalone(metadataIdentifier, v);
+ } else {
+ storeMetadataInCluster(metadataIdentifier, v);
+ }
+ }
+
+ private void storeMetadataInCluster(BaseMetadataIdentifier metadataIdentifier, String v) {
+ try (JedisCluster jedisCluster =
+ new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v, jedisParams);
+ } catch (Throwable e) {
+ String msg =
+ "Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private void storeMetadataStandalone(BaseMetadataIdentifier metadataIdentifier, String v) {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v, jedisParams);
+ } catch (Throwable e) {
+ String msg = "Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private void deleteMetadata(BaseMetadataIdentifier metadataIdentifier) {
+ if (pool != null) {
+ deleteMetadataStandalone(metadataIdentifier);
+ } else {
+ deleteMetadataInCluster(metadataIdentifier);
+ }
+ }
+
+ private void deleteMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
+ try (JedisCluster jedisCluster =
+ new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ jedisCluster.del(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
+ } catch (Throwable e) {
+ String msg = "Failed to delete " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private void deleteMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.del(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+ } catch (Throwable e) {
+ String msg = "Failed to delete " + metadataIdentifier + " from redis , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private String getMetadata(BaseMetadataIdentifier metadataIdentifier) {
+ if (pool != null) {
+ return getMetadataStandalone(metadataIdentifier);
+ } else {
+ return getMetadataInCluster(metadataIdentifier);
+ }
+ }
+
+ private String getMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
+ try (JedisCluster jedisCluster =
+ new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ return jedisCluster.get(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
+ } catch (Throwable e) {
+ String msg = "Failed to get " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private String getMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+ } catch (Throwable e) {
+ String msg = "Failed to get " + metadataIdentifier + " from redis , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ /**
+ * Store class and application names using Redis hashes
+ * key: default 'dubbo:mapping'
+ * field: class (serviceInterface)
+ * value: application_names
+ * @param serviceInterface field(class)
+ * @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+ * @param newConfigContent new application_names
+ * @param ticket previous application_names
+ * @return
+ */
+ @Override
+ public boolean registerServiceAppMapping(
+ String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
+ try {
+ if (null != ticket && !(ticket instanceof String)) {
+ throw new IllegalArgumentException("redis publishConfigCas requires stat type ticket");
+ }
+ String pathKey = buildMappingKey(defaultMappingGroup);
+
+ return storeMapping(pathKey, serviceInterface, newConfigContent, (String) ticket);
+ } catch (Exception e) {
+ logger.warn(TRANSPORT_FAILED_RESPONSE, "", "", "redis publishConfigCas failed.", e);
+ return false;
+ }
+ }
+
+ private boolean storeMapping(String key, String field, String value, String ticket) {
+ if (pool != null) {
+ return storeMappingStandalone(key, field, value, ticket);
+ } else {
+ return storeMappingInCluster(key, field, value, ticket);
+ }
+ }
+
+ /**
+ * use 'watch' to implement cas.
+ * Find information about slot distribution by key.
+ */
+ private boolean storeMappingInCluster(String key, String field, String value, String ticket) {
+ try (JedisCluster jedisCluster =
+ new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ Jedis jedis = new Jedis(jedisCluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)));
+ jedis.watch(key);
+ String oldValue = jedis.hget(key, field);
+ if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
+ Transaction transaction = jedis.multi();
+ transaction.hset(key, field, value);
+ List<Object> result = transaction.exec();
+ if (null != result) {
+ jedisCluster.publish(buildPubSubKey(), field);
+ return true;
+ }
+ } else {
+ jedis.unwatch();
+ }
+ jedis.close();
+ } catch (Throwable e) {
+ String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ return false;
+ }
+
+ /**
+ * use 'watch' to implement cas.
+ * Find information about slot distribution by key.
+ */
+ private boolean storeMappingStandalone(String key, String field, String value, String ticket) {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.watch(key);
+ String oldValue = jedis.hget(key, field);
+ if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
+ Transaction transaction = jedis.multi();
+ transaction.hset(key, field, value);
+ List<Object> result = transaction.exec();
+ if (null != result) {
+ jedis.publish(buildPubSubKey(), field);
+ return true;
+ }
+ }
+ jedis.unwatch();
+ } catch (Throwable e) {
+ String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ return false;
+ }
+
+ /**
+ * build mapping key
+ * @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+ * @return
+ */
+ private String buildMappingKey(String defaultMappingGroup) {
+ return this.root + GROUP_CHAR_SEPARATOR + defaultMappingGroup;
+ }
+
+ /**
+ * build pub/sub key
+ */
+ private String buildPubSubKey() {
+ return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
+ }
+
+ /**
+ * get content and use content to complete cas
+ * @param serviceKey class
+ * @param group {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
+ */
+ @Override
+ public ConfigItem getConfigItem(String serviceKey, String group) {
+ String key = buildMappingKey(group);
+ String content = getMappingData(key, serviceKey);
+
+ return new ConfigItem(content, content);
+ }
+
+ /**
+ * get current application_names
+ */
+ private String getMappingData(String key, String field) {
+ if (pool != null) {
+ return getMappingDataStandalone(key, field);
+ } else {
+ return getMappingDataInCluster(key, field);
+ }
+ }
+
+ private String getMappingDataInCluster(String key, String field) {
+ try (JedisCluster jedisCluster =
+ new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ return jedisCluster.hget(key, field);
+ } catch (Throwable e) {
+ String msg = "Failed to get " + key + ":" + field + " from redis cluster , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ private String getMappingDataStandalone(String key, String field) {
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.hget(key, field);
+ } catch (Throwable e) {
+ String msg = "Failed to get " + key + ":" + field + " from redis , cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+
+ /**
+ * remove listener. If have no listener,thread will dead
+ */
+ @Override
+ public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+ MappingDataListener mappingDataListener = mappingDataListenerMap.get(buildPubSubKey());
+ if (null != mappingDataListener) {
+ NotifySub notifySub = mappingDataListener.getNotifySub();
+ notifySub.removeListener(serviceKey, listener);
+ if (notifySub.isEmpty()) {
+ mappingDataListener.shutdown();
+ }
+ }
+ }
+
+ /**
+ * Start a thread and subscribe to {@link this#buildPubSubKey()}.
+ * Notify {@link MappingListener} if there is a change in the 'application_names' message.
+ */
+ @Override
+ public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
+ MappingDataListener mappingDataListener =
+ ConcurrentHashMapUtils.computeIfAbsent(mappingDataListenerMap, buildPubSubKey(), k -> {
+ MappingDataListener dataListener = new MappingDataListener(buildPubSubKey());
+ dataListener.start();
+ return dataListener;
+ });
+ mappingDataListener.getNotifySub().addListener(serviceKey, listener);
+ return this.getServiceAppMapping(serviceKey, url);
+ }
+
+ @Override
+ public Set<String> getServiceAppMapping(String serviceKey, URL url) {
+ String key = buildMappingKey(DEFAULT_MAPPING_GROUP);
+ return getAppNames(getMappingData(key, serviceKey));
+ }
+
+ @Override
+ public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
+ String content = this.getMetadata(identifier);
+ return JsonUtils.toJavaObject(content, MetadataInfo.class);
+ }
+
+ @Override
+ public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+ this.storeMetadata(identifier, metadataInfo.getContent());
+ }
+
+ @Override
+ public void unPublishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+ this.deleteMetadata(identifier);
+ }
+
+ // for test
+ public MappingDataListener getMappingDataListener() {
+ return mappingDataListenerMap.get(buildPubSubKey());
+ }
+
+ /**
+ * Listen for changes in the 'application_names' message and notify the listener.
+ */
+ class NotifySub extends JedisPubSub {
+
+ private final Map<String, Set<MappingListener>> listeners = new ConcurrentHashMap<>();
+
+ public void addListener(String key, MappingListener listener) {
+ Set<MappingListener> listenerSet = listeners.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
+ listenerSet.add(listener);
+ }
+
+ public void removeListener(String serviceKey, MappingListener listener) {
+ Set<MappingListener> listenerSet = this.listeners.get(serviceKey);
+ if (listenerSet != null) {
+ listenerSet.remove(listener);
+ if (listenerSet.isEmpty()) {
+ this.listeners.remove(serviceKey);
+ }
+ }
+ }
+
+ public Boolean isEmpty() {
+ return this.listeners.isEmpty();
+ }
+
+ @Override
+ public void onMessage(String key, String msg) {
+ logger.info("sub from redis:" + key + " message:" + msg);
+ String applicationNames = getMappingData(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
+ MappingChangedEvent mappingChangedEvent = new MappingChangedEvent(msg, getAppNames(applicationNames));
+ if (!listeners.get(msg).isEmpty()) {
+ for (MappingListener mappingListener : listeners.get(msg)) {
+ mappingListener.onEvent(mappingChangedEvent);
+ }
+ }
+ }
+
+ @Override
+ public void onPMessage(String pattern, String key, String msg) {
+ onMessage(key, msg);
+ }
+
+ @Override
+ public void onPSubscribe(String pattern, int subscribedChannels) {
+ super.onPSubscribe(pattern, subscribedChannels);
+ }
+ }
+
+ /**
+ * Subscribe application names change message.
+ */
+ class MappingDataListener extends Thread {
+
+ private String path;
+
+ private final NotifySub notifySub = new NotifySub();
+ // for test
+ protected volatile boolean running = true;
+
+ public MappingDataListener(String path) {
+ this.path = path;
+ }
+
+ public NotifySub getNotifySub() {
+ return notifySub;
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ if (pool != null) {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.subscribe(notifySub, path);
+ } catch (Throwable e) {
+ String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ } else {
+ try (JedisCluster jedisCluster = new JedisCluster(
+ jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
+ jedisCluster.subscribe(notifySub, path);
+ } catch (Throwable e) {
+ String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ throw new RpcException(msg, e);
+ }
+ }
+ }
+ }
+
+ public void shutdown() {
+ try {
+ running = false;
+ notifySub.unsubscribe(path);
+ } catch (Throwable e) {
+ String msg = "Failed to unsubscribe " + path + ", cause: " + e.getMessage();
+ logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
+ }
+ }
+ }
+}
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportFactory.java b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportFactory.java
new file mode 100644
index 00000000..d871877f
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.metadata.store.redis;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
+
+/**
+ * RedisMetadataReportFactory.
+ */
+public class RedisMetadataReportFactory extends AbstractMetadataReportFactory {
+
+ @Override
+ public MetadataReport createMetadataReport(URL url) {
+ return new RedisMetadataReport(url);
+ }
+}
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
new file mode 100644
index 00000000..2e6effa1
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
@@ -0,0 +1 @@
+redis=org.apache.dubbo.metadata.store.redis.RedisMetadataReportFactory
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadata4TstService.java b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadata4TstService.java
new file mode 100644
index 00000000..c9662c31
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadata4TstService.java
@@ -0,0 +1,24 @@
+/*
+ * 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.metadata.store.redis;
+
+public interface RedisMetadata4TstService {
+
+ int getCounter();
+
+ void printResult(String var);
+}
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportTest.java b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportTest.java
new file mode 100644
index 00000000..5a922875
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReportTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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.metadata.store.redis;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigItem;
+import org.apache.dubbo.common.utils.JsonUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.metadata.MappingChangedEvent;
+import org.apache.dubbo.metadata.MappingListener;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.rpc.RpcException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+import redis.clients.jedis.exceptions.JedisDataException;
+import redis.embedded.RedisServer;
+
+import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
+import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
+import static org.apache.dubbo.common.constants.CommonConstants.SYNC_REPORT_KEY;
+import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
+import static redis.embedded.RedisServer.newRedisServer;
+
+@DisabledOnOs(OS.WINDOWS)
+class RedisMetadataReportTest {
+
+ private static final String REDIS_URL_TEMPLATE = "redis://%slocalhost:%d",
+ REDIS_PASSWORD = "password",
+ REDIS_URL_AUTH_SECTION = "username:" + REDIS_PASSWORD + "@";
+
+ RedisMetadataReport redisMetadataReport;
+ RedisMetadataReport syncRedisMetadataReport;
+ RedisServer redisServer;
+ URL registryUrl;
+
+ @BeforeEach
+ public void constructor(final TestInfo testInfo) {
+ final boolean usesAuthentication = usesAuthentication(testInfo);
+ int redisPort = 0;
+ IOException exception = null;
+
+ for (int i = 0; i < 10; i++) {
+ try {
+ redisPort = NetUtils.getAvailablePort(30000 + new Random().nextInt(10000));
+ redisServer = newRedisServer()
+ .port(redisPort)
+ // set maxheap to fix Windows error 0x70 while starting redis
+ .settingIf(SystemUtils.IS_OS_WINDOWS, "maxheap 128mb")
+ .settingIf(usesAuthentication, "requirepass " + REDIS_PASSWORD)
+ .build();
+ this.redisServer.start();
+ exception = null;
+ } catch (IOException e) {
+ e.printStackTrace();
+ exception = e;
+ }
+ if (exception == null) {
+ break;
+ }
+ }
+
+ Assertions.assertNull(exception);
+ registryUrl = newRedisUrl(usesAuthentication, redisPort);
+ redisMetadataReport = (RedisMetadataReport) new RedisMetadataReportFactory().createMetadataReport(registryUrl);
+ URL syncRegistryUrl = registryUrl.addParameter(SYNC_REPORT_KEY, "true");
+ syncRedisMetadataReport =
+ (RedisMetadataReport) new RedisMetadataReportFactory().createMetadataReport(syncRegistryUrl);
+ }
+
+ private static boolean usesAuthentication(final TestInfo testInfo) {
+ final String methodName = testInfo.getTestMethod().get().getName();
+ return "testAuthRedisMetadata".equals(methodName) || "testWrongAuthRedisMetadata".equals(methodName);
+ }
+
+ private static URL newRedisUrl(final boolean usesAuthentication, final int redisPort) {
+ final String urlAuthSection = usesAuthentication ? REDIS_URL_AUTH_SECTION : "";
+ return URL.valueOf(String.format(REDIS_URL_TEMPLATE, urlAuthSection, redisPort));
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ this.redisServer.stop();
+ }
+
+ @Test
+ void testAsyncStoreProvider() throws ClassNotFoundException {
+ testStoreProvider(redisMetadataReport, "1.0.0.redis.md.p1", 3000);
+ }
+
+ @Test
+ void testSyncStoreProvider() throws ClassNotFoundException {
+ testStoreProvider(syncRedisMetadataReport, "1.0.0.redis.md.p2", 3);
+ }
+
+ private void testStoreProvider(RedisMetadataReport redisMetadataReport, String version, long moreTime)
+ throws ClassNotFoundException {
+ String interfaceName = "org.apache.dubbo.metadata.store.redis.RedisMetadata4TstService";
+ String group = null;
+ String application = "vic.redis.md";
+ MetadataIdentifier providerMetadataIdentifier =
+ storePrivider(redisMetadataReport, interfaceName, version, group, application);
+ Jedis jedis = null;
+ try {
+ jedis = redisMetadataReport.pool.getResource();
+ String keyTmp = providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY);
+ String value = jedis.get(keyTmp);
+ if (value == null) {
+ Thread.sleep(moreTime);
+ value = jedis.get(keyTmp);
+ }
+
+ Assertions.assertNotNull(value);
+
+ FullServiceDefinition fullServiceDefinition = JsonUtils.toJavaObject(value, FullServiceDefinition.class);
+ Assertions.assertEquals(fullServiceDefinition.getParameters().get("paramTest"), "redisTest");
+ } catch (Throwable e) {
+ throw new RpcException("Failed to put to redis . cause: " + e.getMessage(), e);
+ } finally {
+ if (jedis != null) {
+ jedis.del(providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+ }
+ redisMetadataReport.pool.close();
+ }
+ }
+
+ @Test
+ void testAsyncStoreConsumer() throws ClassNotFoundException {
+ testStoreConsumer(redisMetadataReport, "1.0.0.redis.md.c1", 3000);
+ }
+
+ @Test
+ void testSyncStoreConsumer() throws ClassNotFoundException {
+ testStoreConsumer(syncRedisMetadataReport, "1.0.0.redis.md.c2", 3);
+ }
+
+ private void testStoreConsumer(RedisMetadataReport redisMetadataReport, String version, long moreTime)
+ throws ClassNotFoundException {
+ String interfaceName = "org.apache.dubbo.metadata.store.redis.RedisMetadata4TstService";
+ String group = null;
+ String application = "vic.redis.md";
+ MetadataIdentifier consumerMetadataIdentifier =
+ storeConsumer(redisMetadataReport, interfaceName, version, group, application);
+ Jedis jedis = null;
+ try {
+ jedis = redisMetadataReport.pool.getResource();
+ String keyTmp = consumerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY);
+ String value = jedis.get(keyTmp);
+ if (value == null) {
+ Thread.sleep(moreTime);
+ value = jedis.get(keyTmp);
+ }
+ Assertions.assertEquals(value, "{\"paramConsumerTest\":\"redisCm\"}");
+ } catch (Throwable e) {
+ throw new RpcException("Failed to put to redis . cause: " + e.getMessage(), e);
+ } finally {
+ if (jedis != null) {
+ jedis.del(consumerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+ }
+ redisMetadataReport.pool.close();
+ }
+ }
+
+ private MetadataIdentifier storePrivider(
+ RedisMetadataReport redisMetadataReport,
+ String interfaceName,
+ String version,
+ String group,
+ String application)
+ throws ClassNotFoundException {
+ URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + interfaceName
+ + "?paramTest=redisTest&version=" + version + "&application=" + application
+ + (group == null ? "" : "&group=" + group));
+
+ MetadataIdentifier providerMetadataIdentifier =
+ new MetadataIdentifier(interfaceName, version, group, PROVIDER_SIDE, application);
+ Class interfaceClass = Class.forName(interfaceName);
+ FullServiceDefinition fullServiceDefinition =
+ ServiceDefinitionBuilder.buildFullDefinition(interfaceClass, url.getParameters());
+
+ redisMetadataReport.storeProviderMetadata(providerMetadataIdentifier, fullServiceDefinition);
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return providerMetadataIdentifier;
+ }
+
+ private MetadataIdentifier storeConsumer(
+ RedisMetadataReport redisMetadataReport,
+ String interfaceName,
+ String version,
+ String group,
+ String application)
+ throws ClassNotFoundException {
+ URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + interfaceName
+ + "?version=" + version + "&application=" + application + (group == null ? "" : "&group=" + group));
+
+ MetadataIdentifier consumerMetadataIdentifier =
+ new MetadataIdentifier(interfaceName, version, group, CONSUMER_SIDE, application);
+ Class interfaceClass = Class.forName(interfaceName);
+
+ Map<String, String> tmp = new HashMap<>();
+ tmp.put("paramConsumerTest", "redisCm");
+ redisMetadataReport.storeConsumerMetadata(consumerMetadataIdentifier, tmp);
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return consumerMetadataIdentifier;
+ }
+
+ @Test
+ void testAuthRedisMetadata() throws ClassNotFoundException {
+ testStoreProvider(redisMetadataReport, "1.0.0.redis.md.p1", 3000);
+ }
+
+ @Test
+ void testWrongAuthRedisMetadata() throws ClassNotFoundException {
+ redisMetadataReport = (RedisMetadataReport) new RedisMetadataReportFactory().createMetadataReport(registryUrl);
+ try {
+ testStoreProvider(redisMetadataReport, "1.0.0.redis.md.p1", 3000);
+ } catch (RpcException e) {
+ if (e.getCause() instanceof JedisConnectionException
+ && e.getCause().getCause() instanceof JedisDataException) {
+ Assertions.assertEquals(
+ "WRONGPASS invalid username-password pair or user is disabled.",
+ e.getCause().getCause().getMessage());
+ } else {
+ Assertions.fail("no invalid password exception!");
+ }
+ }
+ }
+
+ @Test
+ void testRegisterServiceAppMapping() throws InterruptedException {
+ String serviceKey1 = "org.apache.dubbo.metadata.store.redis.RedisMetadata4TstService";
+ String serviceKey2 = "org.apache.dubbo.metadata.store.redis.RedisMetadata4TstService2";
+
+ String appNames1 = "test1";
+ String appNames2 = "test1,test2";
+ CountDownLatch latch = new CountDownLatch(2);
+ CountDownLatch latch2 = new CountDownLatch(2);
+
+ MappingListener mappingListener = new MappingListener() {
+ @Override
+ public void onEvent(MappingChangedEvent event) {
+ Set<String> apps = event.getApps();
+ if (apps.size() == 1) {
+ Assertions.assertTrue(apps.contains("test1"));
+ } else {
+ Assertions.assertTrue(apps.contains("test1"));
+ Assertions.assertTrue(apps.contains("test2"));
+ }
+ if (serviceKey1.equals(event.getServiceKey())) {
+ latch.countDown();
+ } else if (serviceKey2.equals(event.getServiceKey())) {
+ latch2.countDown();
+ }
+ }
+
+ @Override
+ public void stop() {}
+ };
+
+ Set<String> serviceAppMapping =
+ redisMetadataReport.getServiceAppMapping(serviceKey1, mappingListener, registryUrl);
+
+ Assertions.assertTrue(serviceAppMapping.isEmpty());
+
+ ConfigItem configItem = redisMetadataReport.getConfigItem(serviceKey1, DEFAULT_MAPPING_GROUP);
+
+ redisMetadataReport.registerServiceAppMapping(
+ serviceKey1, DEFAULT_MAPPING_GROUP, appNames1, configItem.getTicket());
+ configItem = redisMetadataReport.getConfigItem(serviceKey1, DEFAULT_MAPPING_GROUP);
+
+ redisMetadataReport.registerServiceAppMapping(
+ serviceKey1, DEFAULT_MAPPING_GROUP, appNames2, configItem.getTicket());
+
+ latch.await();
+
+ serviceAppMapping = redisMetadataReport.getServiceAppMapping(serviceKey2, mappingListener, registryUrl);
+
+ Assertions.assertTrue(serviceAppMapping.isEmpty());
+
+ configItem = redisMetadataReport.getConfigItem(serviceKey2, DEFAULT_MAPPING_GROUP);
+
+ redisMetadataReport.registerServiceAppMapping(
+ serviceKey2, DEFAULT_MAPPING_GROUP, appNames1, configItem.getTicket());
+ configItem = redisMetadataReport.getConfigItem(serviceKey2, DEFAULT_MAPPING_GROUP);
+ redisMetadataReport.registerServiceAppMapping(
+ serviceKey2, DEFAULT_MAPPING_GROUP, appNames2, configItem.getTicket());
+
+ latch2.await();
+ RedisMetadataReport.MappingDataListener mappingDataListener = redisMetadataReport.getMappingDataListener();
+ Assertions.assertTrue(mappingDataListener.running);
+ Assertions.assertTrue(!mappingDataListener.getNotifySub().isEmpty());
+
+ redisMetadataReport.removeServiceAppMappingListener(serviceKey1, mappingListener);
+ Assertions.assertTrue(mappingDataListener.running);
+ Assertions.assertTrue(!mappingDataListener.getNotifySub().isEmpty());
+ redisMetadataReport.removeServiceAppMappingListener(serviceKey2, mappingListener);
+ Assertions.assertTrue(!mappingDataListener.running);
+ Assertions.assertTrue(mappingDataListener.getNotifySub().isEmpty());
+ }
+
+ @Test
+ void testAppMetadata() {
+ String serviceKey = "org.apache.dubbo.metadata.store.redis.RedisMetadata4TstService";
+ String appName = "demo";
+ URL url = URL.valueOf("test://127.0.0.1:8888/" + serviceKey);
+
+ MetadataInfo metadataInfo = new MetadataInfo(appName);
+ metadataInfo.addService(url);
+ SubscriberMetadataIdentifier identifier =
+ new SubscriberMetadataIdentifier(appName, metadataInfo.calAndGetRevision());
+ MetadataInfo appMetadata = redisMetadataReport.getAppMetadata(identifier, Collections.emptyMap());
+ Assertions.assertNull(appMetadata);
+
+ redisMetadataReport.publishAppMetadata(identifier, metadataInfo);
+ appMetadata = redisMetadataReport.getAppMetadata(identifier, Collections.emptyMap());
+ Assertions.assertNotNull(appMetadata);
+ Assertions.assertEquals(appMetadata.toFullString(), metadataInfo.toFullString());
+ redisMetadataReport.unPublishAppMetadata(identifier, metadataInfo);
+ appMetadata = redisMetadataReport.getAppMetadata(identifier, Collections.emptyMap());
+ Assertions.assertNull(appMetadata);
+ }
+}
diff --git a/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/resources/log4j2-test.xml b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/resources/log4j2-test.xml
new file mode 100644
index 00000000..14fdc8c5
--- /dev/null
+++ b/dubbo-metadata-report-extensions/dubbo-metadata-report-redis/src/test/resources/log4j2-test.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT" follow="true">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} |-%highlight{%-5p} [%t] %40.40c:%-3L -| %m%n%rEx{filters(jdk.internal.reflect,java.lang.reflect,sun.reflect,org.junit,org.mockito)}" charset="UTF-8"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/dubbo-metadata-report-extensions/pom.xml b/dubbo-metadata-report-extensions/pom.xml
index 2414b426..1c458b26 100644
--- a/dubbo-metadata-report-extensions/pom.xml
+++ b/dubbo-metadata-report-extensions/pom.xml
@@ -34,5 +34,6 @@
<module>dubbo-metadata-report-consul</module>
<module>dubbo-metadata-report-etcd</module>
<module>dubbo-metadata-rest</module>
+ <module>dubbo-metadata-report-redis</module>
</modules>
</project>