diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboMetadataAutoConfiguration.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboMetadataAutoConfiguration.java index 2af135e6..1a6b351d 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboMetadataAutoConfiguration.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboMetadataAutoConfiguration.java @@ -17,20 +17,21 @@ package com.alibaba.cloud.dubbo.autoconfigure; import java.util.Collection; -import java.util.Optional; import java.util.function.Supplier; import com.alibaba.cloud.dubbo.metadata.DubboProtocolConfigSupplier; import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository; -import com.alibaba.cloud.dubbo.metadata.repository.MetadataServiceInstanceSelector; +import com.alibaba.cloud.dubbo.metadata.repository.RandomServiceInstanceSelector; +import com.alibaba.cloud.dubbo.metadata.repository.ServiceInstanceSelector; import com.alibaba.cloud.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver; import com.alibaba.cloud.dubbo.metadata.resolver.MetadataResolver; -import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory; import com.alibaba.cloud.dubbo.service.DubboMetadataServiceExporter; import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy; import com.alibaba.cloud.dubbo.service.IntrospectiveDubboMetadataService; +import com.alibaba.cloud.dubbo.util.DubboMetadataUtils; import com.alibaba.cloud.dubbo.util.JSONUtils; import feign.Contract; + import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.spring.ServiceBean; import org.apache.dubbo.config.spring.context.event.ServiceBeanExportedEvent; @@ -44,7 +45,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.EventListener; -import org.springframework.util.CollectionUtils; /** * Spring Boot Auto-Configuration class for Dubbo Metadata. @@ -53,7 +53,8 @@ import org.springframework.util.CollectionUtils; */ @Configuration(proxyBeanMethods = false) @Import({ DubboServiceMetadataRepository.class, IntrospectiveDubboMetadataService.class, - DubboMetadataServiceExporter.class, JSONUtils.class }) + DubboMetadataServiceExporter.class, JSONUtils.class, + DubboMetadataServiceProxy.class, DubboMetadataUtils.class }) public class DubboMetadataAutoConfiguration { @Autowired @@ -73,9 +74,8 @@ public class DubboMetadataAutoConfiguration { @Bean @ConditionalOnMissingBean - public MetadataServiceInstanceSelector metadataServiceInstanceSelector() { - return serviceInstances -> CollectionUtils.isEmpty(serviceInstances) - ? Optional.empty() : serviceInstances.stream().findAny(); + public ServiceInstanceSelector metadataServiceInstanceSelector() { + return new RandomServiceInstanceSelector(); } @Bean @@ -84,15 +84,7 @@ public class DubboMetadataAutoConfiguration { return new DubboProtocolConfigSupplier(protocols); } - @Bean - @ConditionalOnMissingBean - public DubboMetadataServiceProxy dubboMetadataConfigServiceProxy( - DubboGenericServiceFactory factory) { - return new DubboMetadataServiceProxy(factory); - } - // Event-Handling - @EventListener(ServiceBeanExportedEvent.class) public void onServiceBeanExported(ServiceBeanExportedEvent event) { ServiceBean serviceBean = event.getServiceBean(); diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboServiceDiscoveryAutoConfiguration.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboServiceDiscoveryAutoConfiguration.java index de8fb254..ba70c57c 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboServiceDiscoveryAutoConfiguration.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/autoconfigure/DubboServiceDiscoveryAutoConfiguration.java @@ -56,6 +56,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -97,12 +98,11 @@ import static org.springframework.util.StringUtils.hasText; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.cloud.client.discovery.DiscoveryClient") @ConditionalOnProperty(name = "spring.cloud.discovery.enabled", matchIfMissing = true) -@AutoConfigureAfter( - name = { EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME, - ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME, - CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME, - NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME }, - value = { DubboServiceRegistrationAutoConfiguration.class }) +@AutoConfigureAfter(name = { EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME, + ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME, + CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME, + NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME }, value = { + DubboServiceRegistrationAutoConfiguration.class }) public class DubboServiceDiscoveryAutoConfiguration { /** @@ -135,6 +135,9 @@ public class DubboServiceDiscoveryAutoConfiguration { */ private final ObjectProvider> heartbeatEventChangedPredicate; + @Value("${spring.application.name:${dubbo.application.name:application}}") + private String currentApplicationName; + public DubboServiceDiscoveryAutoConfiguration( DubboServiceMetadataRepository dubboServiceMetadataRepository, ApplicationEventPublisher applicationEventPublisher, @@ -154,10 +157,12 @@ public class DubboServiceDiscoveryAutoConfiguration { * NotifyListener) */ private void dispatchServiceInstancesChangedEvent(String serviceName, - Collection serviceInstances) { - if (!hasText(serviceName) || serviceInstances == null) { + List serviceInstances) { + if (!hasText(serviceName) || Objects.equals(currentApplicationName, serviceName) + || serviceInstances == null) { return; } + ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, serviceInstances); if (logger.isInfoEnabled()) { diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/env/DubboCloudProperties.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/env/DubboCloudProperties.java index 3c19723f..8b8919e4 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/env/DubboCloudProperties.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/env/DubboCloudProperties.java @@ -23,6 +23,7 @@ import java.util.Set; import org.springframework.boot.context.properties.ConfigurationProperties; import static com.alibaba.cloud.dubbo.util.DubboCloudConstants.CONFIG_PROPERTY_PREFIX; +import static com.alibaba.cloud.dubbo.util.DubboCloudConstants.DUBBO_CLOUD_REGISTRY_PROPERTY_VALUE; import static org.springframework.util.StringUtils.commaDelimitedListToStringArray; import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.trimAllWhitespace; @@ -48,6 +49,8 @@ public class DubboCloudProperties { */ private String subscribedServices = ALL_DUBBO_SERVICES; + private String registryType = DUBBO_CLOUD_REGISTRY_PROPERTY_VALUE; + public String getSubscribedServices() { return subscribedServices; } @@ -80,4 +83,11 @@ public class DubboCloudProperties { return Collections.unmodifiableSet(subscribedServices); } + public String getRegistryType() { + return registryType; + } + + public void setRegistryType(String registryType) { + this.registryType = registryType; + } } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/DubboServiceMetadataRepository.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/DubboServiceMetadataRepository.java index 9089919e..398e09fa 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/DubboServiceMetadataRepository.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/DubboServiceMetadataRepository.java @@ -22,9 +22,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.PostConstruct; @@ -38,10 +36,13 @@ import com.alibaba.cloud.dubbo.registry.event.SubscribedServicesChangedEvent; import com.alibaba.cloud.dubbo.service.DubboMetadataService; import com.alibaba.cloud.dubbo.service.DubboMetadataServiceExporter; import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy; +import com.alibaba.cloud.dubbo.util.DubboMetadataUtils; import com.alibaba.cloud.dubbo.util.JSONUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; + import org.apache.dubbo.common.URL; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,15 +62,15 @@ import org.springframework.util.MultiValueMap; import static com.alibaba.cloud.dubbo.env.DubboCloudProperties.ALL_DUBBO_SERVICES; import static com.alibaba.cloud.dubbo.http.DefaultHttpRequest.builder; -import static java.lang.String.format; import static java.lang.String.valueOf; -import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; -import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; -import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_PREFIX; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME; import static org.springframework.util.CollectionUtils.isEmpty; import static org.springframework.util.StringUtils.hasText; @@ -85,14 +86,15 @@ public class DubboServiceMetadataRepository /** * The prefix of {@link DubboMetadataService} : "dubbo.metadata-service.". */ - public static final String DUBBO_METADATA_SERVICE_PREFIX = "dubbo.metadata-service."; + @Deprecated + public static final String DUBBO_METADATA_SERVICE_PREFIX = METADATA_SERVICE_PREFIX; /** * The {@link URL URLs} property name of {@link DubboMetadataService} : * "dubbo.metadata-service.urls". */ - public static final String DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME = DUBBO_METADATA_SERVICE_PREFIX - + "urls"; + @Deprecated + public static final String DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME = METADATA_SERVICE_URLS_PROPERTY_NAME; /** * The {@link String#format(String, Object...) pattern} of dubbo protocols port. @@ -108,11 +110,6 @@ public class DubboServiceMetadataRepository */ private final Object monitor = new Object(); - /** - * A {@link Set} of service names that had been initialized. - */ - private final Set initializedServices = new LinkedHashSet<>(); - /** * All exported {@link URL urls} {@link Map} whose key is the return value of * {@link URL#getServiceKey()} method and value is the {@link List} of {@link URL @@ -122,12 +119,6 @@ public class DubboServiceMetadataRepository // =================================== Registration // =================================== // - /** - * The subscribed {@link URL urls} {@link Map} of {@link DubboMetadataService}, whose - * key is the return value of {@link URL#getServiceKey()} method and value is the - * {@link List} of {@link URL URLs}. - */ - private final MultiValueMap subscribedDubboMetadataServiceURLs = new LinkedMultiValueMap<>(); // ==================================================================================== // // @@ -172,7 +163,7 @@ public class DubboServiceMetadataRepository private DiscoveryClient discoveryClient; @Autowired - private MetadataServiceInstanceSelector metadataServiceInstanceSelector; + private ServiceInstanceSelector serviceInstanceSelector; @Autowired private JSONUtils jsonUtils; @@ -180,6 +171,9 @@ public class DubboServiceMetadataRepository @Autowired private InetUtils inetUtils; + @Autowired + private DubboMetadataUtils dubboMetadataUtils; + @Value("${spring.application.name}") private String currentApplicationName; @@ -275,28 +269,11 @@ public class DubboServiceMetadataRepository * @param serviceName service of name */ public void initializeMetadata(String serviceName) { - synchronized (monitor) { - if (initializedServices.contains(serviceName)) { - if (logger.isDebugEnabled()) { - logger.debug( - "The metadata of Dubbo service[name : {}] has been initialized", - serviceName); - } - } - else { - if (logger.isInfoEnabled()) { - logger.info( - "The metadata of Dubbo service[name : {}] is about to be initialized", - serviceName); - } + initDubboRestServiceMetadataRepository(serviceName); + } - if (initSubscribedDubboMetadataService(serviceName)) { - // mark this service name having been initialized - initializedServices.add(serviceName); - } - - } - } + private DubboMetadataService getProxy(String serviceName) { + return dubboMetadataConfigServiceProxy.getProxy(serviceName); } /** @@ -307,15 +284,8 @@ public class DubboServiceMetadataRepository */ public void removeMetadataAndInitializedService(String serviceName, URL url) { synchronized (monitor) { - initializedServices.remove(serviceName); + dubboMetadataConfigServiceProxy.removeProxy(serviceName); dubboRestServiceMetadataRepository.remove(serviceName); - // fix #1260 if the subscribedDubboMetadataServiceURLs removed fail,old meta - // information will be retained - if (DubboMetadataService.class.getName().equals(url.getServiceInterface())) { - String serviceKey = url.getServiceKey(); - subscribedDubboMetadataServiceURLs.remove(serviceKey); - } - } } @@ -345,8 +315,7 @@ public class DubboServiceMetadataRepository private void addDubboMetadataServiceURLsMetadata(Map metadata, List dubboMetadataServiceURLs) { String dubboMetadataServiceURLsJSON = jsonUtils.toJSON(dubboMetadataServiceURLs); - metadata.put(DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME, - dubboMetadataServiceURLsJSON); + metadata.put(METADATA_SERVICE_URLS_PROPERTY_NAME, dubboMetadataServiceURLsJSON); } private void addDubboProtocolsPortMetadata(Map metadata) { @@ -359,15 +328,6 @@ public class DubboServiceMetadataRepository }); } - /** - * Get the property name of Dubbo Protocol. - * @param protocol Dubbo Protocol - * @return non-null - */ - public String getDubboProtocolPropertyName(String protocol) { - return format(DUBBO_PROTOCOLS_PORT_PROPERTY_NAME_PATTERN, protocol); - } - /** * Publish the {@link Set} of {@link ServiceRestMetadata}. * @param serviceRestMetadataSet the {@link Set} of {@link ServiceRestMetadata} @@ -389,25 +349,6 @@ public class DubboServiceMetadataRepository return unmodifiableSet(serviceRestMetadata); } - public List findSubscribedDubboMetadataServiceURLs(String serviceName, - String group, String version, String protocol) { - String serviceKey = URL.buildKey(serviceName, group, version); - - List urls = null; - - synchronized (monitor) { - urls = subscribedDubboMetadataServiceURLs.get(serviceKey); - } - - if (isEmpty(urls)) { - return emptyList(); - } - - return hasText(protocol) ? urls.stream() - .filter(url -> url.getProtocol().equalsIgnoreCase(protocol)) - .collect(Collectors.toList()) : unmodifiableList(urls); - } - /** * The specified service is subscribe or not. * @param serviceName the service name @@ -457,24 +398,13 @@ public class DubboServiceMetadataRepository return allExportedURLs.keySet(); } - /** - * Get the {@link URL urls} that {@link DubboMetadataService} exported by the - * specified {@link ServiceInstance}. - * @param serviceInstance {@link ServiceInstance} - * @return the mutable {@link URL urls} - */ - public List getDubboMetadataServiceURLs(ServiceInstance serviceInstance) { - Map metadata = serviceInstance.getMetadata(); - String dubboURLsJSON = metadata.get(DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME); - return jsonUtils.toURLs(dubboURLsJSON); - } - public Integer getDubboProtocolPort(ServiceInstance serviceInstance, String protocol) { - String protocolProperty = getDubboProtocolPropertyName(protocol); - Map metadata = serviceInstance.getMetadata(); - String protocolPort = metadata.get(protocolProperty); - return hasText(protocolPort) ? Integer.valueOf(protocolPort) : null; + return dubboMetadataUtils.getDubboProtocolPort(serviceInstance, protocol); + } + + private String getDubboProtocolPropertyName(String protocol) { + return dubboMetadataUtils.getDubboProtocolPropertyName(protocol); } public List getExportedURLs(String serviceInterface, String group, @@ -490,6 +420,11 @@ public class DubboServiceMetadataRepository protected void initDubboRestServiceMetadataRepository(String serviceName) { if (dubboRestServiceMetadataRepository.containsKey(serviceName)) { + if (logger.isDebugEnabled()) { + logger.debug( + "The metadata of Dubbo service[name : {}] has been initialized", + serviceName); + } return; } @@ -598,8 +533,7 @@ public class DubboServiceMetadataRepository Set metadata = emptySet(); - DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy - .getProxy(serviceName); + DubboMetadataService dubboMetadataService = getProxy(serviceName); if (dubboMetadataService != null) { try { @@ -624,68 +558,29 @@ public class DubboServiceMetadataRepository subscribedServices.remove(currentApplicationName); } - protected Boolean initSubscribedDubboMetadataService(String serviceName) { - // this need to judge whether the initialization is successful or not. The failed - // initialization will not change the initializedServices - Optional optionalServiceInstance = metadataServiceInstanceSelector - .choose(discoveryClient.getInstances(serviceName)); - if (!((Optional) optionalServiceInstance).isPresent()) { - return false; - } - ServiceInstance serviceInstance = optionalServiceInstance.get(); - if (null == serviceInstance) { - return false; - } - List dubboMetadataServiceURLs = getDubboMetadataServiceURLs(serviceInstance); - if (dubboMetadataServiceURLs.isEmpty()) { - return false; - } - for (URL dubboMetadataServiceURL : dubboMetadataServiceURLs) { - try { - initSubscribedDubboMetadataServiceURL(dubboMetadataServiceURL); - DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy - .getProxy(serviceName); - if (dubboMetadataService == null) { - dubboMetadataService = initDubboMetadataServiceProxy( - dubboMetadataServiceURL); - } - - if (dubboMetadataService == null) { - removeMetadataAndInitializedService(serviceName, - dubboMetadataServiceURL); - return false; - } - } - catch (Throwable e) { - if (logger.isErrorEnabled()) { - logger.error(e.getMessage(), e); - } - return false; - } - } - initDubboRestServiceMetadataRepository(serviceName); - return true; - } - - private void initSubscribedDubboMetadataServiceURL(URL dubboMetadataServiceURL) { - // add subscriptions - String serviceKey = dubboMetadataServiceURL.getServiceKey(); - subscribedDubboMetadataServiceURLs.add(serviceKey, dubboMetadataServiceURL); - } - - private DubboMetadataService initDubboMetadataServiceProxy( - URL dubboMetadataServiceURL) { - String serviceName = dubboMetadataServiceURL.getParameter(APPLICATION_KEY); - String version = dubboMetadataServiceURL.getParameter(VERSION_KEY); - // Initialize DubboMetadataService with right version - return dubboMetadataConfigServiceProxy.initProxy(serviceName, version); - - } - @Override public void setApplicationEventPublisher( ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } + public List findSubscribedDubboMetadataServiceURLs(URL subscribedURL) { + // The parameter of "group" as the service name + String serviceInterface = subscribedURL.getServiceInterface(); + String group = subscribedURL.getParameter(GROUP_KEY); + String version = subscribedURL.getParameter(VERSION_KEY); + String protocol = subscribedURL.getParameter(PROTOCOL_KEY); + List serviceInstances = discoveryClient.getInstances(group); + List urls = dubboMetadataUtils.getDubboMetadataServiceURLs(serviceInstances, + serviceInterface, version, protocol); + + if (logger.isInfoEnabled()) { + logger.info( + "The DubboMetadataService of service [name : {} , instances : {}] URLs[protocol : {} , size : {}] has been subscribed.", + group, serviceInstances.size(), protocol, urls.size()); + } + + return urls; + } + } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/RandomServiceInstanceSelector.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/RandomServiceInstanceSelector.java new file mode 100644 index 00000000..90f0cc3f --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/RandomServiceInstanceSelector.java @@ -0,0 +1,43 @@ +/* + * 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 com.alibaba.cloud.dubbo.metadata.repository; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +import org.springframework.cloud.client.ServiceInstance; + +import static java.util.Optional.of; +import static org.springframework.util.CollectionUtils.isEmpty; + +/** + * Random {@link ServiceInstanceSelector} + * + * @author Mercy + */ +public class RandomServiceInstanceSelector implements ServiceInstanceSelector { + + @Override + public Optional select(List serviceInstances) { + if (isEmpty(serviceInstances)) { + return Optional.empty(); + } + ThreadLocalRandom random = ThreadLocalRandom.current(); + return of(serviceInstances.get(random.nextInt(serviceInstances.size()))); + } +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/MetadataServiceInstanceSelector.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/ServiceInstanceSelector.java similarity index 86% rename from spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/MetadataServiceInstanceSelector.java rename to spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/ServiceInstanceSelector.java index 821e3043..575cdcf0 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/MetadataServiceInstanceSelector.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/metadata/repository/ServiceInstanceSelector.java @@ -26,13 +26,13 @@ import org.springframework.cloud.client.ServiceInstance; * * @author liuxx */ -public interface MetadataServiceInstanceSelector { +public interface ServiceInstanceSelector { /** - * choose a service instance to get metadata. + * Select a service instance to get metadata. * @param serviceInstances all service instance * @return the service instance to get metadata */ - Optional choose(List serviceInstances); + Optional select(List serviceInstances); } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/AbstractSpringCloudRegistry.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/AbstractSpringCloudRegistry.java index 8676085c..f6434f3c 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/AbstractSpringCloudRegistry.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/AbstractSpringCloudRegistry.java @@ -63,6 +63,7 @@ import static org.springframework.util.StringUtils.hasText; * * @author Mercy */ +@Deprecated public abstract class AbstractSpringCloudRegistry extends FailbackRegistry { /** @@ -370,12 +371,7 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry { } private void subscribeDubboMetadataServiceURLs(URL url, NotifyListener listener) { - String serviceInterface = url.getServiceInterface(); - String group = url.getParameter(GROUP_KEY); - String version = url.getParameter(VERSION_KEY); - String protocol = url.getParameter(PROTOCOL_KEY); - List urls = repository.findSubscribedDubboMetadataServiceURLs( - serviceInterface, group, version, protocol); + List urls = repository.findSubscribedDubboMetadataServiceURLs(url); listener.notify(urls); } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboCloudRegistry.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboCloudRegistry.java new file mode 100644 index 00000000..311dd268 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboCloudRegistry.java @@ -0,0 +1,499 @@ +/* + * 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 com.alibaba.cloud.dubbo.registry; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository; +import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent; +import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory; +import com.alibaba.cloud.dubbo.service.DubboMetadataService; +import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy; +import com.alibaba.cloud.dubbo.util.DubboMetadataUtils; +import com.alibaba.cloud.dubbo.util.JSONUtils; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.URLBuilder; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.NotifyListener; +import org.apache.dubbo.registry.support.FailbackRegistry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.CollectionUtils; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.stream.StreamSupport.stream; +import static org.apache.dubbo.common.URLBuilder.from; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; +import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; +import static org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME; +import static org.springframework.util.StringUtils.hasText; + +/** + * Dubbo Cloud {@link FailbackRegistry} is based on Spring Cloud {@link DiscoveryClient} + * @author Mercy + */ +public class DubboCloudRegistry extends FailbackRegistry { + + /** + * The parameter name of {@link #servicesLookupInterval}. + */ + public static final String SERVICES_LOOKUP_INTERVAL_PARAM_NAME = "dubbo.services.lookup.interval"; + + protected static final String DUBBO_METADATA_SERVICE_CLASS_NAME = DubboMetadataService.class + .getName(); + + /** + * Caches the IDs of {@link ApplicationListener}. + */ + private static final Set registerListeners = new HashSet<>(); + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final DiscoveryClient discoveryClient; + + private final DubboServiceMetadataRepository repository; + + private final DubboMetadataServiceProxy dubboMetadataConfigServiceProxy; + + private final JSONUtils jsonUtils; + + private final DubboGenericServiceFactory dubboGenericServiceFactory; + + private final DubboMetadataUtils dubboMetadataUtils; + + /** + * The interval in second of lookup service names(only for Dubbo-OPS). + */ + private final long servicesLookupInterval; + + private final ConfigurableApplicationContext applicationContext; + + private final String currentApplicationName; + + public DubboCloudRegistry(URL url, DiscoveryClient discoveryClient, + DubboServiceMetadataRepository repository, + DubboMetadataServiceProxy dubboMetadataConfigServiceProxy, + JSONUtils jsonUtils, DubboGenericServiceFactory dubboGenericServiceFactory, + ConfigurableApplicationContext applicationContext) { + + super(url); + this.servicesLookupInterval = url + .getParameter(SERVICES_LOOKUP_INTERVAL_PARAM_NAME, 60L); + this.discoveryClient = discoveryClient; + this.repository = repository; + this.dubboMetadataConfigServiceProxy = dubboMetadataConfigServiceProxy; + this.jsonUtils = jsonUtils; + this.dubboGenericServiceFactory = dubboGenericServiceFactory; + this.applicationContext = applicationContext; + this.dubboMetadataUtils = getBean(DubboMetadataUtils.class); + this.currentApplicationName = dubboMetadataUtils.getCurrentApplicationName(); + } + + private T getBean(Class beanClass) { + return this.applicationContext.getBean(beanClass); + } + + protected boolean shouldRegister(URL url) { + String side = url.getParameter(SIDE_KEY); + + boolean should = PROVIDER_SIDE.equals(side); // Only register the Provider. + + if (!should) { + if (logger.isDebugEnabled()) { + logger.debug("The URL[{}] should not be registered.", url.toString()); + } + } + + return should; + } + + @Override + public final void doRegister(URL url) { + if (!shouldRegister(url)) { + return; + } + repository.exportURL(url); + } + + @Override + public final void doUnregister(URL url) { + if (!shouldRegister(url)) { + return; + } + repository.unexportURL(url); + } + + @Override + public final void doSubscribe(URL url, NotifyListener listener) { + + if (isAdminURL(url)) { + // TODO in future + if (logger.isWarnEnabled()) { + logger.warn("This feature about admin will be supported in the future."); + } + } + else if (isDubboMetadataServiceURL(url)) { // for DubboMetadataService + subscribeDubboMetadataServiceURLs(url, listener); + } + else { // for general Dubbo Services + subscribeURLs(url, listener); + } + } + + private void subscribeURLs(URL url, NotifyListener listener) { + + // Sync subscription + subscribeURLs(url, getServices(url), listener); + + // Async subscription + registerServiceInstancesChangedListener(url, event -> { + + Set serviceNames = getServices(url); + + String serviceName = event.getServiceName(); + + if (serviceNames.contains(serviceName)) { + subscribeURLs(url, serviceNames, listener); + } + }); + } + + private void subscribeURLs(URL url, Set serviceNames, + NotifyListener listener) { + + List subscribedURLs = new LinkedList<>(); + + serviceNames.forEach(serviceName -> { + + subscribeURLs(url, subscribedURLs, serviceName, + () -> getServiceInstances(serviceName)); + + }); + + // Notify all + notifyAllSubscribedURLs(url, subscribedURLs, listener); + } + + private void registerServiceInstancesChangedListener(URL url, + ApplicationListener listener) { + String listenerId = generateId(url); + if (registerListeners.add(listenerId)) { + applicationContext.addApplicationListener(listener); + } + } + + private void subscribeURLs(URL subscribedURL, List subscribedURLs, + String serviceName, + Supplier> serviceInstancesSupplier) { + List serviceInstances = serviceInstancesSupplier.get(); + subscribeURLs(subscribedURL, subscribedURLs, serviceName, serviceInstances); + } + + private void subscribeURLs(URL subscribedURL, List subscribedURLs, + String serviceName, List serviceInstances) { + + if (CollectionUtils.isEmpty(serviceInstances)) { + if (logger.isWarnEnabled()) { + logger.warn(format("There is no instance in service[name : %s]", + serviceName)); + } + } + + List exportedURLs = getExportedURLs(subscribedURL, serviceName, + serviceInstances); + + /** + * Add the exported URLs from {@link MetadataService} + */ + subscribedURLs.addAll(exportedURLs); + } + + private List getExportedURLs(URL subscribedURL, String serviceName, + List serviceInstances) { + + List validServiceInstances = filter(serviceInstances); + + // If there is no valid ServiceInstance, return empty result + if (isEmpty(validServiceInstances)) { + if (logger.isWarnEnabled()) { + logger.warn( + "There is no instance from service[name : {}], and then Dubbo Service[key : {}] will not be " + + "available , please make sure the further impact", + serviceName, subscribedURL.getServiceKey()); + } + return emptyList(); + } + + List subscribedURLs = cloneExportedURLs(subscribedURL, serviceInstances); + + // clear local service instances, help GC + validServiceInstances.clear(); + + return subscribedURLs; + } + + /** + * Clone the subscribed URLs based on the template URLs + * @param subscribedURL the URL to be subscribed + * @param serviceInstances the list of {@link ServiceInstance service instances} + * @return non-null + */ + private List cloneExportedURLs(URL subscribedURL, + List serviceInstances) { + + List clonedExportedURLs = new LinkedList<>(); + + serviceInstances.forEach(serviceInstance -> { + + String host = serviceInstance.getHost(); + + getTemplateExportedURLs(subscribedURL, serviceInstances).stream() + .map(templateURL -> templateURL.removeParameter(TIMESTAMP_KEY)) + .map(templateURL -> templateURL.removeParameter(PID_KEY)) + .map(templateURL -> { + String protocol = templateURL.getProtocol(); + int port = repository.getDubboProtocolPort(serviceInstance, + protocol); + if (Objects.equals(templateURL.getHost(), host) + && Objects.equals(templateURL.getPort(), port)) { // use + // templateURL + // if + // equals + return templateURL; + } + + URLBuilder clonedURLBuilder = from(templateURL) // remove the + // parameters from + // the template + // URL + .setHost(host) // reset the host + .setPort(port); // reset the port + + return clonedURLBuilder.build(); + }).forEach(clonedExportedURLs::add); + }); + return clonedExportedURLs; + } + + private List getTemplateExportedURLs(URL subscribedURL, + List serviceInstances) { + + DubboMetadataService dubboMetadataService = getProxy(serviceInstances); + + List templateExportedURLs = emptyList(); + + if (dubboMetadataService != null) { + templateExportedURLs = getExportedURLs(dubboMetadataService, subscribedURL); + } + else { + if (logger.isWarnEnabled()) { + logger.warn( + "The metadata of Dubbo service[key : {}] still can't be found, it could effect the further " + + "Dubbo service invocation", + subscribedURL.getServiceKey()); + } + + } + + return templateExportedURLs; + } + + private DubboMetadataService getProxy(List serviceInstances) { + return dubboMetadataConfigServiceProxy.getProxy(serviceInstances); + } + + private List filter(Collection serviceInstances) { + return serviceInstances.stream().filter(this::isDubboServiceInstance) + .collect(Collectors.toList()); + } + + private boolean isDubboServiceInstance(ServiceInstance serviceInstance) { + Map metadata = serviceInstance.getMetadata(); + return metadata.containsKey(METADATA_SERVICE_URLS_PROPERTY_NAME); + } + + private Set getServices(URL url) { + Set subscribedServices = repository.getSubscribedServices(); + // TODO Add the filter feature + return subscribedServices; + } + + private void notifyAllSubscribedURLs(URL url, List subscribedURLs, + NotifyListener listener) { + + if (isEmpty(subscribedURLs)) { + // Add the EMPTY_PROTOCOL URL + subscribedURLs.add(emptyURL(url)); + + if (isDubboMetadataServiceURL(url)) { + // if meta service change, and serviceInstances is zero, will clean up + // information about this client + String serviceName = url.getParameter(GROUP_KEY); + repository.removeMetadataAndInitializedService(serviceName, url); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("The subscribed URL[{}] will notify all URLs : {}", url, + subscribedURLs); + } + + // Notify all + listener.notify(subscribedURLs); + } + + private List getServiceInstances(Iterable serviceNames) { + return stream(serviceNames.spliterator(), false).map(this::getServiceInstances) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List getServiceInstances(String serviceName) { + return hasText(serviceName) ? doGetServiceInstances(serviceName) : emptyList(); + } + + private List doGetServiceInstances(String serviceName) { + List serviceInstances = emptyList(); + try { + serviceInstances = discoveryClient.getInstances(serviceName); + } + catch (Exception e) { + if (logger.isErrorEnabled()) { + logger.error(e.getMessage(), e); + } + } + return serviceInstances; + } + + private String generateId(URL url) { + return url.getServiceKey(); + } + + private URL emptyURL(URL url) { + // issue : When the last service provider is closed, the client still periodically + // connects to the last provider.n + // fix https://github.com/alibaba/spring-cloud-alibaba/issues/1259 + return from(url).setProtocol(EMPTY_PROTOCOL).removeParameter(CATEGORY_KEY) + .build(); + } + + private List getExportedURLs(DubboMetadataService dubboMetadataService, + URL subscribedURL) { + String serviceInterface = subscribedURL.getServiceInterface(); + String group = subscribedURL.getParameter(GROUP_KEY); + String version = subscribedURL.getParameter(VERSION_KEY); + // The subscribed protocol may be null + String subscribedProtocol = subscribedURL.getParameter(PROTOCOL_KEY); + String exportedURLsJSON = dubboMetadataService.getExportedURLs(serviceInterface, + group, version); + return jsonUtils.toURLs(exportedURLsJSON).stream() + .filter(exportedURL -> subscribedProtocol == null + || subscribedProtocol.equalsIgnoreCase(exportedURL.getProtocol())) + .collect(Collectors.toList()); + } + + private void subscribeDubboMetadataServiceURLs(URL subscribedURL, + NotifyListener listener) { + + // Sync subscription + subscribeDubboMetadataServiceURLs(subscribedURL, listener, + singleton(getServiceName(subscribedURL))); + + // Sync subscription + if (containsProviderCategory(subscribedURL)) { + registerServiceInstancesChangedListener(subscribedURL, event -> { + + Set serviceNames = getServices(subscribedURL); + + if (!serviceNames.contains(event.getServiceName())) { + return; + } + + subscribeDubboMetadataServiceURLs(subscribedURL, listener, serviceNames); + }); + } + } + + private String getServiceName(URL subscribedURL) { + return subscribedURL.getParameter(GROUP_KEY); + } + + private void subscribeDubboMetadataServiceURLs(URL subscribedURL, + NotifyListener listener, Set serviceNames) { + + String serviceInterface = subscribedURL.getServiceInterface(); + String version = subscribedURL.getParameter(VERSION_KEY); + String protocol = subscribedURL.getParameter(PROTOCOL_KEY); + + List serviceInstances = getServiceInstances(serviceNames); + + List urls = dubboMetadataUtils.getDubboMetadataServiceURLs(serviceInstances, + serviceInterface, version, protocol); + + notifyAllSubscribedURLs(subscribedURL, urls, listener); + } + + private boolean containsProviderCategory(URL subscribedURL) { + String category = subscribedURL.getParameter(CATEGORY_KEY); + return category == null ? false : category.contains(PROVIDER); + } + + @Override + public final void doUnsubscribe(URL url, NotifyListener listener) { + // TODO + } + + @Override + public boolean isAvailable() { + return !discoveryClient.getServices().isEmpty(); + } + + protected boolean isAdminURL(URL url) { + return ADMIN_PROTOCOL.equals(url.getProtocol()); + } + + protected boolean isDubboMetadataServiceURL(URL url) { + return DUBBO_METADATA_SERVICE_CLASS_NAME.equals(url.getServiceInterface()); + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistry.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistry.java index 3e4a1a5c..07afb6fc 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistry.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistry.java @@ -31,7 +31,10 @@ import org.springframework.context.ConfigurableApplicationContext; * protocol is "spring-cloud". * * @author Mercy + * @deprecated It's a legacy and not recommended implementation, being replacing to be + * {@link DubboCloudRegistry} */ +@Deprecated public class SpringCloudRegistry extends AbstractSpringCloudRegistry { private final DubboServiceMetadataRepository dubboServiceMetadataRepository; diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistryFactory.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistryFactory.java index 975b076f..f78c9346 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistryFactory.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/SpringCloudRegistryFactory.java @@ -16,10 +16,12 @@ package com.alibaba.cloud.dubbo.registry; +import com.alibaba.cloud.dubbo.env.DubboCloudProperties; import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository; import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory; import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy; import com.alibaba.cloud.dubbo.util.JSONUtils; + import org.apache.dubbo.common.URL; import org.apache.dubbo.registry.Registry; import org.apache.dubbo.registry.RegistryFactory; @@ -28,6 +30,7 @@ import org.apache.dubbo.registry.support.AbstractRegistryFactory; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.context.ConfigurableApplicationContext; +import static com.alibaba.cloud.dubbo.util.DubboCloudConstants.SPRING_CLOUD_REGISTRY_PROPERTY_VALUE; import static java.lang.System.getProperty; /** @@ -50,6 +53,8 @@ public class SpringCloudRegistryFactory extends AbstractRegistryFactory { */ public static String ADDRESS = "localhost"; + public static String MODE = "dubbo.cloud."; + private static String SERVICES_LOOKUP_SCHEDULER_THREAD_NAME_PREFIX = getProperty( "dubbo.services.lookup.scheduler.thread.name.prefix ", "dubbo-services-lookup-"); @@ -88,9 +93,26 @@ public class SpringCloudRegistryFactory extends AbstractRegistryFactory { @Override public Registry createRegistry(URL url) { init(); - return new SpringCloudRegistry(url, discoveryClient, - dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, - jsonUtils, dubboGenericServiceFactory, applicationContext); + + DubboCloudProperties dubboCloudProperties = applicationContext + .getBean(DubboCloudProperties.class); + + Registry registry = null; + + switch (dubboCloudProperties.getRegistryType()) { + case SPRING_CLOUD_REGISTRY_PROPERTY_VALUE: + registry = new SpringCloudRegistry(url, discoveryClient, + dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, + jsonUtils, dubboGenericServiceFactory, applicationContext); + break; + default: + registry = new DubboCloudRegistry(url, discoveryClient, + dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, + jsonUtils, dubboGenericServiceFactory, applicationContext); + break; + } + + return registry; } } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/event/ServiceInstancesChangedEvent.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/event/ServiceInstancesChangedEvent.java index f69954b3..0f778f82 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/event/ServiceInstancesChangedEvent.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/event/ServiceInstancesChangedEvent.java @@ -16,14 +16,14 @@ package com.alibaba.cloud.dubbo.registry.event; -import java.util.Collection; +import java.util.List; import org.springframework.cloud.client.ServiceInstance; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.SimpleApplicationEventMulticaster; -import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableList; /** * An event raised after the {@link ServiceInstance instances} of one service has been @@ -35,7 +35,7 @@ public class ServiceInstancesChangedEvent extends ApplicationEvent { private final String serviceName; - private final Collection serviceInstances; + private final List serviceInstances; /** * Current event has been processed or not. Typically, Spring Event was based on sync @@ -51,10 +51,10 @@ public class ServiceInstancesChangedEvent extends ApplicationEvent { * @throws IllegalArgumentException if source is null. */ public ServiceInstancesChangedEvent(String serviceName, - Collection serviceInstances) { + List serviceInstances) { super(serviceName); this.serviceName = serviceName; - this.serviceInstances = unmodifiableCollection(serviceInstances); + this.serviceInstances = unmodifiableList(serviceInstances); } /** @@ -67,7 +67,7 @@ public class ServiceInstancesChangedEvent extends ApplicationEvent { /** * @return all {@link ServiceInstance service instances}. */ - public Collection getServiceInstances() { + public List getServiceInstances() { return serviceInstances; } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboGenericServiceFactory.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboGenericServiceFactory.java index 9ddee6b3..f426ce1d 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboGenericServiceFactory.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboGenericServiceFactory.java @@ -30,11 +30,13 @@ import javax.annotation.PreDestroy; import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata; import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata; + import org.apache.dubbo.common.URL; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.spring.ReferenceBean; import org.apache.dubbo.rpc.service.GenericService; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,10 +97,9 @@ public class DubboGenericServiceFactory { private ReferenceBean build(String interfaceName, String version, String group, Map dubboTranslatedAttributes) { - Integer key = Objects.hash(interfaceName, version, group, - dubboTranslatedAttributes); + String key = createKey(interfaceName, version, group, dubboTranslatedAttributes); - return cache.computeIfAbsent(group + key, k -> { + return cache.computeIfAbsent(key, k -> { ReferenceBean referenceBean = new ReferenceBean<>(); referenceBean.setGeneric(true); referenceBean.setInterface(interfaceName); @@ -110,6 +111,12 @@ public class DubboGenericServiceFactory { }); } + private String createKey(String interfaceName, String version, String group, + Map dubboTranslatedAttributes) { + return group + "#" + + Objects.hash(interfaceName, version, group, dubboTranslatedAttributes); + } + private void bindReferenceBean(ReferenceBean referenceBean, Map dubboTranslatedAttributes) { DataBinder dataBinder = new DataBinder(referenceBean); @@ -155,7 +162,7 @@ public class DubboGenericServiceFactory { cache.clear(); } - public synchronized void destroy(String serviceName) { + public void destroy(String serviceName) { Set removeGroups = new HashSet<>(cache.keySet()); for (String key : removeGroups) { if (key.contains(serviceName)) { diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboMetadataServiceProxy.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboMetadataServiceProxy.java index 359ee5e0..1e99df3a 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboMetadataServiceProxy.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/service/DubboMetadataServiceProxy.java @@ -16,13 +16,28 @@ package com.alibaba.cloud.dubbo.service; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository; +import com.alibaba.cloud.dubbo.metadata.repository.ServiceInstanceSelector; +import com.alibaba.cloud.dubbo.util.DubboMetadataUtils; + +import org.apache.dubbo.common.URL; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.DisposableBean; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; import static java.lang.reflect.Proxy.newProxyInstance; +import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; /** * The proxy of {@link DubboMetadataService}. @@ -31,26 +46,30 @@ import static java.lang.reflect.Proxy.newProxyInstance; */ public class DubboMetadataServiceProxy implements BeanClassLoaderAware, DisposableBean { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final DubboGenericServiceFactory dubboGenericServiceFactory; + private final DubboMetadataUtils dubboMetadataUtils; + + private final ServiceInstanceSelector serviceInstanceSelector; + + private final DiscoveryClient discoveryClient; + private final Map dubboMetadataServiceCache = new ConcurrentHashMap<>(); private ClassLoader classLoader; public DubboMetadataServiceProxy( - DubboGenericServiceFactory dubboGenericServiceFactory) { + DubboGenericServiceFactory dubboGenericServiceFactory, + DubboServiceMetadataRepository dubboServiceMetadataRepository, + DubboMetadataUtils dubboMetadataUtils, + ServiceInstanceSelector serviceInstanceSelector, + DiscoveryClient discoveryClient) { this.dubboGenericServiceFactory = dubboGenericServiceFactory; - } - - /** - * Initializes {@link DubboMetadataService}'s Proxy. - * @param serviceName the service name - * @param version the service version - * @return a {@link DubboMetadataService} proxy - */ - public DubboMetadataService initProxy(String serviceName, String version) { - return dubboMetadataServiceCache.computeIfAbsent(serviceName, - name -> newProxy(name, version)); + this.dubboMetadataUtils = dubboMetadataUtils; + this.serviceInstanceSelector = serviceInstanceSelector; + this.discoveryClient = discoveryClient; } /** @@ -59,6 +78,79 @@ public class DubboMetadataServiceProxy implements BeanClassLoaderAware, Disposab */ public void removeProxy(String serviceName) { dubboMetadataServiceCache.remove(serviceName); + dubboGenericServiceFactory.destroy(serviceName); + } + + /** + * Get the proxy of {@link DubboMetadataService} if possible + * @param serviceInstances the instances of {@link DubboMetadataService} + * @return null if initialization can't be done + */ + public DubboMetadataService getProxy(List serviceInstances) { + + DubboMetadataService dubboMetadataService = null; + + // attempt to get the proxy of DubboMetadataService in maximum times + int attempts = serviceInstances.size(); + + for (int i = 0; i < attempts; i++) { + Optional serviceInstance = select(serviceInstances); + + if (serviceInstance.isPresent()) { + + List dubboMetadataServiceURLs = getDubboMetadataServiceURLs( + serviceInstance.get()); + + for (URL dubboMetadataServiceURL : dubboMetadataServiceURLs) { + dubboMetadataService = createProxyIfAbsent(dubboMetadataServiceURL); + if (dubboMetadataService != null) { + return dubboMetadataService; + } + } + } + } + + return dubboMetadataService; + } + + /** + * Is the {@link DubboMetadataService}'s Proxy initialized or not + * @param serviceName the service name + * @return true if initialized , or return false + */ + public boolean isInitialized(String serviceName) { + return dubboMetadataServiceCache.containsKey(serviceName); + } + + /** + * Create a {@link DubboMetadataService}'s Proxy If abstract. + * @param dubboMetadataServiceURL the {@link URL} of {@link DubboMetadataService} + * @return a {@link DubboMetadataService} proxy + */ + private DubboMetadataService createProxyIfAbsent(URL dubboMetadataServiceURL) { + String serviceName = dubboMetadataServiceURL.getParameter(APPLICATION_KEY); + String version = dubboMetadataServiceURL.getParameter(VERSION_KEY); + // Initialize DubboMetadataService with right version + return createProxyIfAbsent(serviceName, version); + } + + /** + * Initializes {@link DubboMetadataService}'s Proxy. + * @param serviceName the service name + * @param version the service version + * @return a {@link DubboMetadataService} proxy + */ + private DubboMetadataService createProxyIfAbsent(String serviceName, String version) { + return dubboMetadataServiceCache.computeIfAbsent(serviceName, + name -> createProxy(name, version)); + } + + private Optional select(List serviceInstances) { + return serviceInstanceSelector.select(serviceInstances); + } + + private List getDubboMetadataServiceURLs(ServiceInstance serviceInstance) { + return dubboMetadataUtils.getDubboMetadataServiceURLs(serviceInstance); } /** @@ -68,7 +160,12 @@ public class DubboMetadataServiceProxy implements BeanClassLoaderAware, Disposab * @return a {@link DubboMetadataService} proxy */ public DubboMetadataService getProxy(String serviceName) { - return dubboMetadataServiceCache.get(serviceName); + return dubboMetadataServiceCache.getOrDefault(serviceName, + getProxy0(serviceName)); + } + + private DubboMetadataService getProxy0(String serviceName) { + return getProxy(discoveryClient.getInstances(serviceName)); } @Override @@ -88,7 +185,14 @@ public class DubboMetadataServiceProxy implements BeanClassLoaderAware, Disposab * @param version the service version * @return a {@link DubboMetadataService} proxy */ - protected DubboMetadataService newProxy(String serviceName, String version) { + protected DubboMetadataService createProxy(String serviceName, String version) { + + if (logger.isInfoEnabled()) { + logger.info( + "The metadata of Dubbo service[name : {}] is about to be initialized", + serviceName); + } + return (DubboMetadataService) newProxyInstance(classLoader, new Class[] { DubboMetadataService.class }, new DubboMetadataServiceInvocationHandler(serviceName, version, diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboCloudConstants.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboCloudConstants.java index d491b0a1..bba5c1ad 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboCloudConstants.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboCloudConstants.java @@ -28,4 +28,19 @@ public interface DubboCloudConstants { */ String CONFIG_PROPERTY_PREFIX = "dubbo.cloud"; + /** + * The property name of Registry type + */ + String REGISTRY_TYPE_PROPERTY_NAME = CONFIG_PROPERTY_PREFIX + ".registry-type"; + + /** + * The property value of Spring Cloud Registry + */ + String SPRING_CLOUD_REGISTRY_PROPERTY_VALUE = "spring-cloud"; + + /** + * The property value of Dubbo Cloud Registry + */ + String DUBBO_CLOUD_REGISTRY_PROPERTY_VALUE = "dubbo-cloud"; + } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboMetadataUtils.java b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboMetadataUtils.java new file mode 100644 index 00000000..fb8e3539 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/util/DubboMetadataUtils.java @@ -0,0 +1,118 @@ +/* + * 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 com.alibaba.cloud.dubbo.util; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.alibaba.cloud.dubbo.service.DubboMetadataService; + +import org.apache.dubbo.common.URL; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.env.Environment; + +import static java.lang.String.format; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME; +import static org.springframework.util.StringUtils.hasText; + +/** + * The utilities class of Dubbo Metadata + * + * @author Mercy + */ +public class DubboMetadataUtils { + + /** + * The {@link String#format(String, Object...) pattern} of dubbo protocols port. + */ + public static final String DUBBO_PROTOCOLS_PORT_PROPERTY_NAME_PATTERN = "dubbo.protocols.%s.port"; + + private final JSONUtils jsonUtils; + + private final Environment environment; + + private final String currentApplicationName; + + public DubboMetadataUtils(JSONUtils jsonUtils, Environment environment) { + this.jsonUtils = jsonUtils; + this.environment = environment; + this.currentApplicationName = environment.getProperty("spring.application.name", + environment.getProperty("dubbo.application.name", "application")); + } + + /** + * Get the current application name + * @return non-null + */ + public String getCurrentApplicationName() { + return currentApplicationName; + } + + /** + * Get the {@link URL urls} that {@link DubboMetadataService} exported by the + * specified {@link ServiceInstance}. + * @param serviceInstance {@link ServiceInstance} + * @return the mutable {@link URL urls} + */ + public List getDubboMetadataServiceURLs(ServiceInstance serviceInstance) { + Map metadata = serviceInstance.getMetadata(); + String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME); + return jsonUtils.toURLs(dubboURLsJSON); + } + + /** + * Get the {@link URL urls} that {@link DubboMetadataService} exported by the + * specified {@link ServiceInstance ServiceInstances}. + * @param serviceInstances the list of {@link ServiceInstance ServiceInstances} + * @param serviceInterface the interface name of Dubbo service + * @param version the version of Dubbo service + * @param protocol the protocol that Dubbo Service exports + * @return the mutable {@link URL urls} + */ + public List getDubboMetadataServiceURLs(List serviceInstances, + String serviceInterface, String version, String protocol) { + return serviceInstances.stream().map(this::getDubboMetadataServiceURLs) + .flatMap(List::stream) + .filter(url -> protocol == null + || Objects.equals(protocol, url.getProtocol())) + .filter(url -> Objects.equals(serviceInterface, + url.getServiceInterface())) + .filter(url -> Objects.equals(version, url.getParameter(VERSION_KEY))) + .collect(Collectors.toList()); + } + + /** + * Get the property name of Dubbo Protocol. + * @param protocol Dubbo Protocol + * @return non-null + */ + public String getDubboProtocolPropertyName(String protocol) { + return format(DUBBO_PROTOCOLS_PORT_PROPERTY_NAME_PATTERN, protocol); + } + + public Integer getDubboProtocolPort(ServiceInstance serviceInstance, + String protocol) { + String protocolProperty = getDubboProtocolPropertyName(protocol); + Map metadata = serviceInstance.getMetadata(); + String protocolPort = metadata.get(protocolProperty); + return hasText(protocolPort) ? Integer.valueOf(protocolPort) : null; + } +}