From a3aa6c177b43dc59f2f37da923c78d4b71a9d901 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Fri, 23 Aug 2019 09:49:35 +0800 Subject: [PATCH] Dubbo cloud native (#4922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Polish apache/dubbo#4542 : [Enhancement] Adapt the Java standard Event/Listener mechanism * Polish apache/dubbo#4541 : [Feature] Add local File System DynamicConfigurationFactory‘s extension * Polish apache#4541 : Bugfix * Polish apache/dubbo#4541 : Optimization * Polish apache/dubbo#4541 : Add the compatibility for PollingWatchService on the some platforms * Polish apache/dubbo#4541 : Add delay publish without ThreadPoolExecutor * Polish apache/dubbo#4541 : Refactor the extension name * Polish apache/dubbo#4541 : Add remove ops * Polish apache/dubbo#4541 : Add testable constructor * Polish apache/dubbo#4541 : Add getConfigGroups method * Polish apache/dubbo#4610 : [Refactor] Refactor the bootstrap module * Polish apache/dubbo#4541 : Fix the nulling URL issue * Polish apache/dubbo#4622 : [Refactor] Refactor ConfigManager * Polish apache/dubbo#4622 : [Refactor] Refactor ConfigManager * Polish apache/dubbo#4622 : Support multiple configcenters * Polish apache/dubbo#4671 : ServiceNameMapping will not map the group, version and protocol * update referenceCount log (#4683) Add comments to support multiple shared connections * Polish /apache/dubbo#4687 : Remove the duplicated test code in dubbo-config-spring (#4688) * #4685 修改代码if判断false问题 if (hasException == false)修改成if (!hasException) (#4695) * Fixed Service annotation method parameters are not in effect (#4598) * keep demo simple, and switch to use zookeeper as registry center (#4705) * keep demo simple, and switch to use zookeeper as registry center * remove comment * @Reference auto-wires the instance of generic interface #4594 (#4677) * try to shorten maven output to make travis build pass (#4710) * use CountDownLatch to check zk registry if establish connection (#4589) * Minor change * Rename the extension name of WritableMetadataService * Polish apache/dubbo#4759 : [Refactor] Change the signature of methods of MetadataService #4759 * Merge remote-tracking branch 'upstream/master' into dubbo-cloud-native # Conflicts: # dubbo-all/pom.xml # dubbo-bom/pom.xml # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ConfigCenterConfig.java # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/RegistryConfig.java # dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java # dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java # dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java # dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/mock/MockDynamicConfiguration.java # dubbo-configcenter/dubbo-configcenter-consul/src/main/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfiguration.java # dubbo-configcenter/dubbo-configcenter-etcd/src/test/java/org/apache/dubbo/configcenter/support/etcd/EtcdDynamicConfigurationTest.java # dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java # dubbo-configcenter/dubbo-configcenter-nacos/src/test/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationTest.java # dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java # dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/MethodDefinition.java # dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifier.java # dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java # dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifierTest.java # dubbo-metadata/dubbo-metadata-definition-protobuf/src/main/java/org/apache/dubbo/metadata/definition/protobuf/ProtobufTypeBuilder.java # dubbo-metadata/dubbo-metadata-definition-protobuf/src/test/java/org/apache/dubbo/metadata/definition/protobuf/ProtobufTypeBuilderTest.java # dubbo-metadata/pom.xml # dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java # dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java # dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistryFactory.java # dubbo-rpc/dubbo-rpc-xml/src/main/java/org/apache/dubbo/xml/rpc/protocol/xmlrpc/XmlRpcProtocol.java * Polish apache/dubbo#3984 : Add the implementation of Page getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) * Code merge * Fix the cases * Merge remote-tracking branch 'upstream/cloud-native' into dubbo-cloud-native # Conflicts: # dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java # dubbo-metadata/dubbo-metadata-definition-protobuf/pom.xml # dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java # dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java # dubbo-registry/dubbo-registry-etcd3/src/main/java/org/apache/dubbo/registry/etcd/EtcdServiceDiscovery.java * Refactor ConfigManager * Refactor ConfigManager * Resolve the issues on ConfigManager * Refactor and add test-cases for ConfigManager * Polish apache/dubbo#4774 : [Feature] Dubbo Cloud Native - To Support in Spring * Polish apache/dubbo#4808 : [Feature] Add the registered/unregistered event mechanism ShutdownHook * Polish apache/dubbo#4807 : [Feature] Add the callback mechanism ShutdownHook #4807 * Polish apache/dubbo#4813 : [Feature] add Prioritized implementation for ServiceInstanceCustomizer * Polish apache/dubbo#4815 : [Feature] Add the ServiceLoader for Dubbo's services or components * Polish apache/dubbo#4815 : [Feature] Add the ServiceLoader for Dubbo's services or components * Polish apache/dubbo#4813 : [Feature] add Prioritized implementation for ServiceInstanceCustomizer * Polish apache/dubbo#4807 : Add sort implementation * Refactor * Refactor * Polish apache/dubbo#4845 : [Feature] Enhance the Event-Publishing feature to original ServiceDiscovery * Merge remote-tracking branch 'upstream/cloud-native' into dubbo-cloud-native # Conflicts: # dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java # dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java * Merge remote-tracking branch 'upstream/cloud-native' into dubbo-cloud-native # Conflicts: # dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java # dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java * Polish apache/dubbo#4854 : [Feature] MetadataService supports the Dubbo protocol under auto-increased port * Polish apache/dubbo#4857 : [Enhancement] Sync the Metadata storage type into ApplicationConfig * Polish apache/dubbo#4868 : [Enhancement] Refactor ConfigChangeEvent * Polish apache/dubbo#4868 : [Enhancement] Refactor ConfigChangeEvent * Polish apache/dubbo#4873 : [Feature] Add a conditional EventListener into Event Module * Polish apache/dubbo#4875 : [Feature] Refactor ServiceInstancesChangedListener * Remove the cycle dependencies * Remove the cycle dependencies * Polish apache/dubbo#4903 : [Feature] Set source into the BeanDefinition of Dubbo Config * Polish apache/dubbo#4902 : [Feature] Dubbo Cloud Native to Spring XML scenario * Polish apache/dubbo#4713 : Initial the new module and dependencies * Polish apache/dubbo#4690 : AnnotatedBeanDefinitionRegistryUtils#registerBeans can't remove the duplicated bean definitions * Polish apache/dubbo#4690 : AnnotatedBeanDefinitionRegistryUtils#registerBeans can't remove the duplicated bean definitions * Polish apache/dubbo#4690 : AnnotatedBeanDefinitionRegistryUtils#registerBeans can't remove the duplicated bean definitions * Polish apache/dubbo#4910 : [Feature] To suppoort DubboLifecycleComponentApplicationListener in Spring XML scenario * Polish apache/dubbo#4713 : Add Service discovery implementation for Eureka #4713 * Polish apache/dubbo#4713 : Add Service registration and discovery implementation for Eureka * Polish apache/dubbo#4713 : Add Service registration and discovery implementation for Eureka --- dubbo-bootstrap/pom.xml | 6 + .../DubboServiceConsumerBootstrap.java | 15 +- .../DubboServiceProviderBootstrap.java | 2 +- .../DubboServiceProviderMinimumBootstrap.java | 2 +- .../common/constants/RegistryConstants.java | 9 +- .../client/DefaultServiceInstance.java | 2 +- .../client/ServiceDiscoveryRegistry.java | 10 +- .../ServiceInstanceMetadataUtils.java | 16 +- dubbo-registry/dubbo-registry-eureka/pom.xml | 20 +- .../ConfigurableEurekaInstanceConfig.java | 369 ++++++++++++++++++ .../eureka/EurekaServiceDiscovery.java | 278 +++++++++++++ .../dubbo/registry/eureka/package-info.java | 22 ++ ...che.dubbo.registry.client.ServiceDiscovery | 1 + .../eureka/EurekaServiceDiscoveryTest.java | 66 ++++ 14 files changed, 802 insertions(+), 16 deletions(-) create mode 100644 dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/ConfigurableEurekaInstanceConfig.java create mode 100644 dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/package-info.java create mode 100644 dubbo-registry/dubbo-registry-eureka/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery create mode 100644 dubbo-registry/dubbo-registry-eureka/src/test/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscoveryTest.java diff --git a/dubbo-bootstrap/pom.xml b/dubbo-bootstrap/pom.xml index 9818a3a8c3d..01fc024f079 100644 --- a/dubbo-bootstrap/pom.xml +++ b/dubbo-bootstrap/pom.xml @@ -45,6 +45,12 @@ ${project.parent.version} test + + org.apache.dubbo + dubbo-registry-eureka + ${project.parent.version} + test + com.google.guava guava diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java index 780452923c9..4b5fd4b5f1e 100644 --- a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java @@ -17,7 +17,6 @@ package org.apache.dubbo.bootstrap; import org.apache.dubbo.bootstrap.rest.UserService; -import org.apache.dubbo.config.MetadataReportConfig; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.context.ConfigManager; @@ -33,12 +32,18 @@ public static void main(String[] args) throws Exception { new DubboBootstrap() .application("dubbo-consumer-demo") .protocol(builder -> builder.port(20887).name("dubbo")) + // Eureka + .registry(builder -> builder.address("eureka://127.0.0.1:8761?registry-type=service&subscribed-services=dubbo-provider-demo")) + // Zookeeper - .registry("zookeeper", builder -> builder.address("zookeeper://127.0.0.1:2181?registry.type=service&subscribed.services=dubbo-provider-demo").preferred(true)) - .metadataReport(new MetadataReportConfig("zookeeper://127.0.0.1:2181")) + // .registry("zookeeper", builder -> builder.address("zookeeper://127.0.0.1:2181?registry-type=service&subscribed-services=dubbo-provider-demo")) + // .metadataReport(new MetadataReportConfig("zookeeper://127.0.0.1:2181")) + // Nacos -// .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry.type=service&subscribed.services=dubbo-provider-demo")) -// .registry("consul", builder -> builder.address("consul://127.0.0.1:8500?registry.type=service&subscribed.services=dubbo-provider-demo").group("namespace1")) + // .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry.type=service&subscribed.services=dubbo-provider-demo")) + + // Consul + // .registry("consul", builder -> builder.address("consul://127.0.0.1:8500?registry.type=service&subscribed.services=dubbo-provider-demo").group("namespace1")) .reference("echo", builder -> builder.interfaceClass(EchoService.class).protocol("dubbo")) .reference("user", builder -> builder.interfaceClass(UserService.class).protocol("rest")) .start() diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java index 15abaa5bab2..ba3ddd683de 100644 --- a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java @@ -49,7 +49,7 @@ private static void multipleRegistries() { RegistryConfig serviceRegistry = new RegistryConfig(); serviceRegistry.setId("serviceRegistry"); - serviceRegistry.setAddress("zookeeper://127.0.0.1:2181?registry.type=service"); + serviceRegistry.setAddress("zookeeper://127.0.0.1:2181?registry-type=service"); ServiceConfig echoService = new ServiceConfig<>(); echoService.setInterface(EchoService.class.getName()); diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderMinimumBootstrap.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderMinimumBootstrap.java index c0764e8f609..4e1958bb142 100644 --- a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderMinimumBootstrap.java +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderMinimumBootstrap.java @@ -25,7 +25,7 @@ public static void main(String[] args) { new DubboBootstrap() .application("dubbo-provider-demo") // .registry(builder -> builder.address("zookeeper://127.0.0.1:2181?registry.type=service")) - .registry(builder -> builder.address("file://?registry.type=service")) + .registry(builder -> builder.address("eureka://127.0.0.1:8761?registry-type=service")) .protocol(builder -> builder.port(-1).name("dubbo")) .service(builder -> builder.interfaceClass(EchoService.class).ref(new EchoServiceImpl())) .start() diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java index cd10a11e347..2bf7cce0aea 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java @@ -58,7 +58,7 @@ public interface RegistryConstants { * * @since 2.7.4 */ - String REGISTRY_TYPE_KEY = "registry.type"; + String REGISTRY_TYPE_KEY = "registry-type"; /** * The parameter value of Service-Oriented Registry type @@ -67,6 +67,11 @@ public interface RegistryConstants { */ String SERVICE_REGISTRY_TYPE = "service"; + /** + * The protocol for Service Discovery + * + * @since 2.7.4 + */ String SERVICE_REGISTRY_PROTOCOL = "service-discovery-registry"; /** @@ -74,7 +79,7 @@ public interface RegistryConstants { * * @since 2.7.4 */ - String SUBSCRIBED_SERVICE_NAMES_KEY = "subscribed.services"; + String SUBSCRIBED_SERVICE_NAMES_KEY = "subscribed-services"; /** diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java index b9f5da7be98..608ee868029 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java @@ -59,7 +59,7 @@ public DefaultServiceInstance(String id, String serviceName, String host, Intege } public DefaultServiceInstance(String serviceName, String host, Integer port) { - this(null, serviceName, host, port); + this(host + ":" + port, serviceName, host, port); } public void setId(String id) { diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java index 0a39fb13496..4eac6a780fa 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java @@ -29,6 +29,7 @@ import org.apache.dubbo.registry.Registry; import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; +import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils; import org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory; import org.apache.dubbo.registry.client.selector.ServiceInstanceSelector; import org.apache.dubbo.registry.support.FailbackRegistry; @@ -112,7 +113,13 @@ public ServiceDiscoveryRegistry(URL registryURL) { this.writableMetadataService = WritableMetadataService.getExtension(metadataStorageType); } - protected Set getSubscribedServices(URL registryURL) { + /** + * Get the subscribed services from the specified registry {@link URL url} + * + * @param registryURL the specified registry {@link URL url} + * @return non-null + */ + public static Set getSubscribedServices(URL registryURL) { String subscribedServiceNames = registryURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY); return isBlank(subscribedServiceNames) ? emptySet() : unmodifiableSet(of(subscribedServiceNames.split(",")) @@ -307,6 +314,7 @@ private List getSubscribedURLs(URL subscribedURL, Collection serviceInstances = instances.stream() .filter(ServiceInstance::isEnabled) .filter(ServiceInstance::isHealthy) + .filter(ServiceInstanceMetadataUtils::isDubboServiceInstance) .collect(Collectors.toList()); /** diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java index 1d7fca9531c..55cdfd1d2d3 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java @@ -208,14 +208,26 @@ public static String getMetadataStorageType(ServiceInstance serviceInstance) { /** * Set the metadata storage type in specified {@link ServiceInstance service instance} * - * @param serviceInstance {@link ServiceInstance service instance} - * @param metadataType remote or local + * @param serviceInstance {@link ServiceInstance service instance} + * @param metadataType remote or local */ public static void setMetadataStorageType(ServiceInstance serviceInstance, String metadataType) { Map metadata = serviceInstance.getMetadata(); metadata.put(METADATA_STORAGE_TYPE_KEY, metadataType); } + /** + * Is Dubbo Service instance or not + * + * @param serviceInstance {@link ServiceInstance service instance} + * @return if Dubbo Service instance, return true, or false + */ + public static boolean isDubboServiceInstance(ServiceInstance serviceInstance) { + Map metadata = serviceInstance.getMetadata(); + return metadata.containsKey(METADATA_SERVICE_URL_PARAMS_KEY) + || metadata.containsKey(METADATA_SERVICE_URLS_PROPERTY_NAME); + } + private static void setProviderHostParam(Map params, URL providerURL) { params.put(HOST_PARAM_NAME, providerURL.getHost()); } diff --git a/dubbo-registry/dubbo-registry-eureka/pom.xml b/dubbo-registry/dubbo-registry-eureka/pom.xml index 94dbd74e508..cd4181da7f0 100644 --- a/dubbo-registry/dubbo-registry-eureka/pom.xml +++ b/dubbo-registry/dubbo-registry-eureka/pom.xml @@ -27,18 +27,32 @@ com.netflix.eureka eureka-client - true javax.inject javax.inject 1 - true com.netflix.eureka eureka-core - true + + + + + com.thoughtworks.xstream + xstream + 1.4.10 + + + com.sun.jersey.contribs + jersey-apache-client4 + 1.19.1 + + + com.netflix.archaius + archaius-core + 0.7.6 diff --git a/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/ConfigurableEurekaInstanceConfig.java b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/ConfigurableEurekaInstanceConfig.java new file mode 100644 index 00000000000..8e0324d76c6 --- /dev/null +++ b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/ConfigurableEurekaInstanceConfig.java @@ -0,0 +1,369 @@ +/* + * 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.registry.eureka; + +import com.netflix.appinfo.DataCenterInfo; +import com.netflix.appinfo.EurekaInstanceConfig; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.MyDataCenterInfo; + +import java.util.HashMap; +import java.util.Map; + +/** + * Configurable {@link EurekaInstanceConfig} implementation + */ +class ConfigurableEurekaInstanceConfig implements EurekaInstanceConfig { + + private String appname; + + private String appGroupName; + + private boolean instanceEnabledOnit; + + private int nonSecurePort; + + private int securePort; + + private boolean nonSecurePortEnabled = true; + + private boolean securePortEnabled; + + private int leaseRenewalIntervalInSeconds = 30; + + private int leaseExpirationDurationInSeconds = 90; + + private String virtualHostName = "unknown"; + + private String instanceId; + + private String secureVirtualHostName = "unknown"; + + private String aSGName; + + private Map metadataMap = new HashMap<>(); + + private DataCenterInfo dataCenterInfo = new MyDataCenterInfo(DataCenterInfo.Name.MyOwn); + + private String ipAddress; + + private String statusPageUrlPath; + + private String statusPageUrl; + + private String homePageUrlPath = "/"; + + private String homePageUrl; + + private String healthCheckUrlPath; + + private String healthCheckUrl; + + private String secureHealthCheckUrl; + + private String namespace = "eureka"; + + private String hostname; + + private boolean preferIpAddress = false; + + private InstanceInfo.InstanceStatus initialStatus = InstanceInfo.InstanceStatus.UP; + + private String[] defaultAddressResolutionOrder = new String[0]; + + @Override + public String getAppname() { + return appname; + } + + public ConfigurableEurekaInstanceConfig setAppname(String appname) { + this.appname = appname; + return this; + } + + @Override + public String getAppGroupName() { + return appGroupName; + } + + public ConfigurableEurekaInstanceConfig setAppGroupName(String appGroupName) { + this.appGroupName = appGroupName; + return this; + } + + @Override + public boolean isInstanceEnabledOnit() { + return instanceEnabledOnit; + } + + public ConfigurableEurekaInstanceConfig setInstanceEnabledOnit(boolean instanceEnabledOnit) { + this.instanceEnabledOnit = instanceEnabledOnit; + return this; + } + + @Override + public int getNonSecurePort() { + return nonSecurePort; + } + + public ConfigurableEurekaInstanceConfig setNonSecurePort(int nonSecurePort) { + this.nonSecurePort = nonSecurePort; + return this; + } + + @Override + public int getSecurePort() { + return securePort; + } + + public ConfigurableEurekaInstanceConfig setSecurePort(int securePort) { + this.securePort = securePort; + return this; + } + + @Override + public boolean isNonSecurePortEnabled() { + return nonSecurePortEnabled; + } + + @Override + public boolean getSecurePortEnabled() { + return securePortEnabled; + } + + public ConfigurableEurekaInstanceConfig setNonSecurePortEnabled(boolean nonSecurePortEnabled) { + this.nonSecurePortEnabled = nonSecurePortEnabled; + return this; + } + + public ConfigurableEurekaInstanceConfig setSecurePortEnabled(boolean securePortEnabled) { + this.securePortEnabled = securePortEnabled; + return this; + } + + @Override + public int getLeaseRenewalIntervalInSeconds() { + return leaseRenewalIntervalInSeconds; + } + + public ConfigurableEurekaInstanceConfig setLeaseRenewalIntervalInSeconds(int leaseRenewalIntervalInSeconds) { + this.leaseRenewalIntervalInSeconds = leaseRenewalIntervalInSeconds; + return this; + } + + @Override + public int getLeaseExpirationDurationInSeconds() { + return leaseExpirationDurationInSeconds; + } + + public ConfigurableEurekaInstanceConfig setLeaseExpirationDurationInSeconds(int leaseExpirationDurationInSeconds) { + this.leaseExpirationDurationInSeconds = leaseExpirationDurationInSeconds; + return this; + } + + @Override + public String getVirtualHostName() { + return virtualHostName; + } + + public ConfigurableEurekaInstanceConfig setVirtualHostName(String virtualHostName) { + this.virtualHostName = virtualHostName; + return this; + } + + @Override + public String getInstanceId() { + return instanceId; + } + + public ConfigurableEurekaInstanceConfig setInstanceId(String instanceId) { + this.instanceId = instanceId; + return this; + } + + @Override + public String getSecureVirtualHostName() { + return secureVirtualHostName; + } + + @Override + public String getASGName() { + return aSGName; + } + + @Override + public String getHostName(boolean refresh) { + return null; + } + + public ConfigurableEurekaInstanceConfig setSecureVirtualHostName(String secureVirtualHostName) { + this.secureVirtualHostName = secureVirtualHostName; + return this; + } + + public ConfigurableEurekaInstanceConfig setASGName(String aSGName) { + this.aSGName = aSGName; + return this; + } + + @Override + public Map getMetadataMap() { + return metadataMap; + } + + public ConfigurableEurekaInstanceConfig setMetadataMap(Map metadataMap) { + this.metadataMap = metadataMap; + return this; + } + + @Override + public DataCenterInfo getDataCenterInfo() { + return dataCenterInfo; + } + + public ConfigurableEurekaInstanceConfig setDataCenterInfo(DataCenterInfo dataCenterInfo) { + this.dataCenterInfo = dataCenterInfo; + return this; + } + + @Override + public String getIpAddress() { + return ipAddress; + } + + public ConfigurableEurekaInstanceConfig setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + return this; + } + + @Override + public String getStatusPageUrlPath() { + return statusPageUrlPath; + } + + public ConfigurableEurekaInstanceConfig setStatusPageUrlPath(String statusPageUrlPath) { + this.statusPageUrlPath = statusPageUrlPath; + return this; + } + + @Override + public String getStatusPageUrl() { + return statusPageUrl; + } + + public ConfigurableEurekaInstanceConfig setStatusPageUrl(String statusPageUrl) { + this.statusPageUrl = statusPageUrl; + return this; + } + + @Override + public String getHomePageUrlPath() { + return homePageUrlPath; + } + + public ConfigurableEurekaInstanceConfig setHomePageUrlPath(String homePageUrlPath) { + this.homePageUrlPath = homePageUrlPath; + return this; + } + + @Override + public String getHomePageUrl() { + return homePageUrl; + } + + public ConfigurableEurekaInstanceConfig setHomePageUrl(String homePageUrl) { + this.homePageUrl = homePageUrl; + return this; + } + + @Override + public String getHealthCheckUrlPath() { + return healthCheckUrlPath; + } + + public ConfigurableEurekaInstanceConfig setHealthCheckUrlPath(String healthCheckUrlPath) { + this.healthCheckUrlPath = healthCheckUrlPath; + return this; + } + + @Override + public String getHealthCheckUrl() { + return healthCheckUrl; + } + + public ConfigurableEurekaInstanceConfig setHealthCheckUrl(String healthCheckUrl) { + this.healthCheckUrl = healthCheckUrl; + return this; + } + + @Override + public String getSecureHealthCheckUrl() { + return secureHealthCheckUrl; + } + + public ConfigurableEurekaInstanceConfig setSecureHealthCheckUrl(String secureHealthCheckUrl) { + this.secureHealthCheckUrl = secureHealthCheckUrl; + return this; + } + + @Override + public String getNamespace() { + return namespace; + } + + public ConfigurableEurekaInstanceConfig setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + public String getHostname() { + return hostname; + } + + public ConfigurableEurekaInstanceConfig setHostname(String hostname) { + this.hostname = hostname; + return this; + } + + public boolean isPreferIpAddress() { + return preferIpAddress; + } + + public ConfigurableEurekaInstanceConfig setPreferIpAddress(boolean preferIpAddress) { + this.preferIpAddress = preferIpAddress; + return this; + } + + public InstanceInfo.InstanceStatus getInitialStatus() { + return initialStatus; + } + + public ConfigurableEurekaInstanceConfig setInitialStatus(InstanceInfo.InstanceStatus initialStatus) { + this.initialStatus = initialStatus; + return this; + } + + @Override + public String[] getDefaultAddressResolutionOrder() { + return defaultAddressResolutionOrder; + } + + public ConfigurableEurekaInstanceConfig setDefaultAddressResolutionOrder(String[] defaultAddressResolutionOrder) { + this.defaultAddressResolutionOrder = defaultAddressResolutionOrder; + return this; + } +} diff --git a/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscovery.java b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscovery.java new file mode 100644 index 00000000000..07fc37d50b8 --- /dev/null +++ b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscovery.java @@ -0,0 +1,278 @@ +/* + * 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.registry.eureka; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.EurekaInstanceConfig; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.config.ConfigurationManager; +import com.netflix.discovery.CacheRefreshedEvent; +import com.netflix.discovery.DefaultEurekaClientConfig; +import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.EurekaClient; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.discovery.EurekaEvent; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +import static java.util.Collections.emptyList; +import static org.apache.dubbo.event.EventDispatcher.getDefaultExtension; +import static org.apache.dubbo.registry.client.ServiceDiscoveryRegistry.getSubscribedServices; + +/** + * Eureka {@link ServiceDiscovery} implementation based on Eureka API + */ +public class EurekaServiceDiscovery implements ServiceDiscovery { + + private final EventDispatcher eventDispatcher = getDefaultExtension(); + + private ApplicationInfoManager applicationInfoManager; + + private EurekaClient eurekaClient; + + private Set subscribedServices; + + /** + * last apps hash code is used to identify the {@link Applications} is changed or not + */ + private String lastAppsHashCode; + + @Override + public void initialize(URL registryURL) throws Exception { + Properties eurekaConfigProperties = buildEurekaConfigProperties(registryURL); + initConfigurationManager(eurekaConfigProperties); + initSubscribedServices(registryURL); + } + + /** + * Build the Properties whose {@link java.util.Map.Entry entries} are retrieved from + * {@link URL#getParameters() the parameters of the specified URL}, which will be used in the Eureka's {@link ConfigurationManager} + * + * @param registryURL the {@link URL url} to connect Eureka + * @return non-null + */ + private Properties buildEurekaConfigProperties(URL registryURL) { + Properties properties = new Properties(); + Map parameters = registryURL.getParameters(); + setDefaultProperties(registryURL, properties); + parameters.entrySet().stream() + .filter(this::filterEurekaProperty) + .forEach(propertyEntry -> { + properties.setProperty(propertyEntry.getKey(), propertyEntry.getValue()); + }); + return properties; + } + + /** + * Initialize {@link #subscribedServices} property + * + * @param registryURL the {@link URL url} to connect Eureka + */ + private void initSubscribedServices(URL registryURL) { + this.subscribedServices = getSubscribedServices(registryURL); + } + + private boolean filterEurekaProperty(Map.Entry propertyEntry) { + String propertyName = propertyEntry.getKey(); + return propertyName.startsWith("eureka."); + } + + private void setDefaultProperties(URL registryURL, Properties properties) { + setDefaultServiceURL(registryURL, properties); + setDefaultInitialInstanceInfoReplicationIntervalSeconds(properties); + } + + private void setDefaultServiceURL(URL registryURL, Properties properties) { + StringBuilder defaultServiceURLBuilder = new StringBuilder("http://") + .append(registryURL.getHost()) + .append(":") + .append(registryURL.getPort()) + .append("/eureka"); + properties.setProperty("eureka.serviceUrl.default", defaultServiceURLBuilder.toString()); + } + + /** + * Set the default property for {@link EurekaClientConfig#getInitialInstanceInfoReplicationIntervalSeconds()} + * which means do register immediately + * + * @param properties {@link Properties} + */ + private void setDefaultInitialInstanceInfoReplicationIntervalSeconds(Properties properties) { + properties.setProperty("eureka.appinfo.initial.replicate.time", "0"); + } + + /** + * Initialize {@link ConfigurationManager} + * + * @param eurekaConfigProperties the Eureka's {@link ConfigurationManager} + */ + private void initConfigurationManager(Properties eurekaConfigProperties) { + ConfigurationManager.loadProperties(eurekaConfigProperties); + } + + private void initApplicationInfoManager(ServiceInstance serviceInstance) { + EurekaInstanceConfig eurekaInstanceConfig = buildEurekaInstanceConfig(serviceInstance); + this.applicationInfoManager = new ApplicationInfoManager(eurekaInstanceConfig, (ApplicationInfoManager.OptionalArgs) null); + } + + /** + * Initialize {@link #eurekaClient} property + * + * @param serviceInstance {@link ServiceInstance} + */ + private void initEurekaClient(ServiceInstance serviceInstance) { + if (eurekaClient != null) { + return; + } + initApplicationInfoManager(serviceInstance); + EurekaClient eurekaClient = createEurekaClient(); + registerEurekaEventListener(eurekaClient); + // set eurekaClient + this.eurekaClient = eurekaClient; + } + + private void registerEurekaEventListener(EurekaClient eurekaClient) { + eurekaClient.registerEventListener(this::onEurekaEvent); + } + + private void onEurekaEvent(EurekaEvent event) { + if (event instanceof CacheRefreshedEvent) { + onCacheRefreshedEvent(CacheRefreshedEvent.class.cast(event)); + } + } + + private void onCacheRefreshedEvent(CacheRefreshedEvent event) { + synchronized (this) { // Make sure thread-safe in async execution + Applications applications = eurekaClient.getApplications(); + String appsHashCode = applications.getAppsHashCode(); + if (!Objects.equals(lastAppsHashCode, appsHashCode)) { // Changed + // Dispatch Events + dispatchServiceInstancesChangedEvent(); + lastAppsHashCode = appsHashCode; // update current result + } + } + } + + private void dispatchServiceInstancesChangedEvent() { + subscribedServices.forEach(serviceName -> { + eventDispatcher.dispatch(new ServiceInstancesChangedEvent(serviceName, getInstances(serviceName))); + }); + } + + private EurekaClient createEurekaClient() { + EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); + DiscoveryClient eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); + return eurekaClient; + } + + @Override + public void destroy() throws Exception { + if (eurekaClient != null) { + this.eurekaClient.shutdown(); + } + } + + @Override + public void register(ServiceInstance serviceInstance) throws RuntimeException { + initEurekaClient(serviceInstance); + setInstanceStatus(InstanceInfo.InstanceStatus.UP); + } + + private void setInstanceStatus(InstanceInfo.InstanceStatus status) { + if (applicationInfoManager != null) { + this.applicationInfoManager.setInstanceStatus(status); + } + } + + @Override + public void update(ServiceInstance serviceInstance) throws RuntimeException { + setInstanceStatus(serviceInstance.isHealthy() ? InstanceInfo.InstanceStatus.UP : + InstanceInfo.InstanceStatus.UNKNOWN); + } + + @Override + public void unregister(ServiceInstance serviceInstance) throws RuntimeException { + setInstanceStatus(InstanceInfo.InstanceStatus.UP); + } + + @Override + public Set getServices() { + Applications applications = this.eurekaClient.getApplications(); + if (applications == null) { + return Collections.emptySet(); + } + List registered = applications.getRegisteredApplications(); + Set names = new LinkedHashSet<>(); + for (Application app : registered) { + if (app.getInstances().isEmpty()) { + continue; + } + names.add(app.getName().toLowerCase()); + } + return names; + } + + @Override + public List getInstances(String serviceName) throws NullPointerException { + Application application = this.eurekaClient.getApplication(serviceName); + + if (application == null) { + return emptyList(); + } + + List infos = application.getInstances(); + List instances = new ArrayList<>(); + for (InstanceInfo info : infos) { + instances.add(buildServiceInstance(info)); + } + return instances; + } + + private ServiceInstance buildServiceInstance(InstanceInfo instance) { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(instance.getId(), instance.getAppName(), + instance.getHostName(), + instance.isPortEnabled(InstanceInfo.PortType.SECURE) ? instance.getSecurePort() : instance.getPort()); + serviceInstance.setMetadata(instance.getMetadata()); + return serviceInstance; + } + + private EurekaInstanceConfig buildEurekaInstanceConfig(ServiceInstance serviceInstance) { + ConfigurableEurekaInstanceConfig eurekaInstanceConfig = new ConfigurableEurekaInstanceConfig() + .setInstanceId(serviceInstance.getId()) + .setAppname(serviceInstance.getServiceName()) + .setIpAddress(serviceInstance.getHost()) + .setNonSecurePort(serviceInstance.getPort()) + .setMetadataMap(serviceInstance.getMetadata()); + return eurekaInstanceConfig; + } +} diff --git a/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/package-info.java b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/package-info.java new file mode 100644 index 00000000000..2c300216513 --- /dev/null +++ b/dubbo-registry/dubbo-registry-eureka/src/main/java/org/apache/dubbo/registry/eureka/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +/** + * The package contains the registry implementations for Netflix Eureka + * + * @since 2.7.4 + */ +package org.apache.dubbo.registry.eureka; diff --git a/dubbo-registry/dubbo-registry-eureka/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-eureka/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 00000000000..51be37ccc1f --- /dev/null +++ b/dubbo-registry/dubbo-registry-eureka/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +eureka=org.apache.dubbo.registry.eureka.EurekaServiceDiscovery \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-eureka/src/test/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-eureka/src/test/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscoveryTest.java new file mode 100644 index 00000000000..80230f28e9d --- /dev/null +++ b/dubbo-registry/dubbo-registry-eureka/src/test/java/org/apache/dubbo/registry/eureka/EurekaServiceDiscoveryTest.java @@ -0,0 +1,66 @@ +/* + * 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.registry.eureka; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstance; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * {@link EurekaServiceDiscovery} Test + * + * @since 2.7.4 + */ +public class EurekaServiceDiscoveryTest { + + private EurekaServiceDiscovery serviceDiscovery; + + private ServiceInstance serviceInstance; + + private URL registryURL = URL.valueOf("eureka://127.0.0.1:8761/eureka"); + + @BeforeEach + public void init() throws Exception { + serviceDiscovery = new EurekaServiceDiscovery(); + serviceDiscovery.initialize(registryURL); + serviceInstance = new DefaultServiceInstance("test", "127.0.0.1", 8080); + serviceDiscovery.register(serviceInstance); + } + + @AfterEach + public void destroy() throws Exception { + serviceDiscovery.destroy(); + } + + @Test + public void testGetServices() { + assertNotNull(serviceDiscovery.getServices()); + } + + @Test + public void testGetInstances() { + serviceDiscovery.getServices().forEach(serviceName -> { + assertNotNull(serviceDiscovery.getInstances(serviceName)); + }); + } +}