diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc index 93f206e0d..3a562b483 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc @@ -301,6 +301,26 @@ Endpoint 暴露的 json 中包含了两种属性: [service_name].ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.nacos.ribbon.NacosRule ---- +=== IPv4至IPv6地址迁移方案 + +==== IPv4和IPv6地址双注册 +在配置完成NacosRule作为负载均衡策略后,应用启动后会默认将微服务的IPv4地址和IPv6地址注册到注册中心中,其中IPv4地址会存放在Nacos服务列表中的IP字段下,IPv6地址在Nacos的metadata字段中,其对应的Key为IPv6。当服务消费者调用服务提供者时,会根据自身的IP地址栈支持情况,选择合适的IP地址类型发起服务调用。具体规则: +(1)服务消费者本身支持IPv4和IPv6双地址栈或仅支持IPv6地址栈的情况下,服务消费者会使用服务提供的IPv6地址发起服务调用,IPv6地址调用失败如本身还同事支持IPv4地址栈时,暂不支持切换到IPv4再发起重试调用; +(2)服务消费者本身仅支持IPv4单地址栈的情况下,服务消费者会使用服务提供的IPv4地址发起服务调用。 + +==== 仅注册IPv4 +如果您只想使用IPv4地址进行注册,可以在application.properties使用以下配置: +[source,properties] +---- +spring.cloud.nacos.discovery.ip-type=IPv4 +---- + +==== 仅注册IPv6 +如果您只想使用IPv6地址,可以在application.properties使用以下配置: +[source,properties] +---- +spring.cloud.nacos.discovery.ip-type=IPv6 +---- === 关于 Nacos Discovery Starter 更多的配置项信息 diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc index 640f6aacd..29899c830 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc @@ -304,6 +304,27 @@ The followings shows how a service instance accesses the Endpoint: [service_name].ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.nacos.ribbon.NacosRule ---- +=== IPv4 to IPv6 address migration scheme + +==== Both register IPv4 and IPv6 address +After configuring NacosRule as the load balancing policy, the IPv4 address and IPv6 address of the microservice will be registered with the registry by default after the application is started, where the IPv4 address will be stored in the IP field of the Nacos service list, the IPv6 address will be in the metadata field of Nacos, and its corresponding Key will be IPv6. When a service consumer calls a service provider, it selects the appropriate IP address type to initiate a service call based on its IP address stack support. Specific rules: +(1) If the service consumer itself supports IPv4 and IPv6 dual address stacks or only supports IPv6 address stacks, the service consumer will use the IPv6 address provided by the service to initiate a service call, and if the IPv6 address call fails, if it also supports the IPv4 address stack, it is temporarily not supported to switch to IPv4 and then initiate a retry call; +(2) If the service consumer itself only supports IPv4 single-address stack, the service consumer will use the IPv4 address provided by the service to initiate service calls. + +==== Only Register IPv4 address +If you only want to register IPv4 address.Config in application.properties as follows: +[source,properties] +---- +spring.cloud.nacos.discovery.ip-type=IPv4 +---- + +==== Only Register IPv6 address +If you only want to register IPv6 address.Config in application.properties as follows: +[source,properties] +---- +spring.cloud.nacos.discovery.ip-type=IPv6 +---- + === More Information about Nacos Discovery Starter Configurations The following shows the other configurations of the starter of Nacos Discovery: diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties index 408e9d2c2..7aed2ab7a 100755 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties @@ -3,6 +3,8 @@ spring.application.name=service-provider spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.enabled=true #spring.cloud.nacos.discovery.instance-enabled=true +#register IPv4 instance +#spring.cloud.nacos.discovery.ip-type=IPv4 #register IPv6 instance #spring.cloud.nacos.discovery.ip-type=IPv6 diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md index 634f3db15..fe2251cbe 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md @@ -163,6 +163,24 @@ NacosServerList 实现了 com.netflix.loadbalancer.ServerList 接口, 如果需要有更加自定义的可以使用 @Autowired 注入一个 NacosRegistration 实例,通过其持有的 NamingService 字段内容直接调用 Nacos API。 +## IPv4至IPv6地址迁移方案 + +### IPv4和IPv6地址双注册 +在配置完成NacosRule作为负载均衡策略后,应用启动后会默认将微服务的IPv4地址和IPv6地址注册到注册中心中,其中IPv4地址会存放在Nacos服务列表中的IP字段下,IPv6地址在Nacos的metadata字段中,其对应的Key为IPv6。当服务消费者调用服务提供者时,会根据自身的IP地址栈支持情况,选择合适的IP地址类型发起服务调用。具体规则: +(1)服务消费者本身支持IPv4和IPv6双地址栈或仅支持IPv6地址栈的情况下,服务消费者会使用服务提供的IPv6地址发起服务调用,IPv6地址调用失败如本身还同事支持IPv4地址栈时,暂不支持切换到IPv4再发起重试调用; +(2)服务消费者本身仅支持IPv4单地址栈的情况下,服务消费者会使用服务提供的IPv4地址发起服务调用。 + +### 仅注册IPv4 +如果您只想使用IPv4地址进行注册,可以在application.properties使用以下配置: +``` +spring.cloud.nacos.discovery.ip-type=IPv4 +``` + +### 仅注册IPv6 +如果您只想使用IPv6地址,可以在application.properties使用以下配置: +``` +spring.cloud.nacos.discovery.ip-type=IPv6 +``` ## Endpoint 信息查看 diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md index afef59aaa..6e1557b33 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md @@ -168,6 +168,24 @@ NacosServerList implements the com.netflix.loadbalancer.ServerList inte If you need to be more customizable, you can use @Autowired to inject a NacosRegistration bean and call the Nacos API directly through the contents of the NamingService field it holds. +## IPv4 to IPv6 address migration scheme + +### Both register IPv4 and IPv6 address +After configuring NacosRule as the load balancing policy, the IPv4 address and IPv6 address of the microservice will be registered with the registry by default after the application is started, where the IPv4 address will be stored in the IP field of the Nacos service list, the IPv6 address will be in the metadata field of Nacos, and its corresponding Key will be IPv6. When a service consumer calls a service provider, it selects the appropriate IP address type to initiate a service call based on its IP address stack support. Specific rules: +(1) If the service consumer itself supports IPv4 and IPv6 dual address stacks or only supports IPv6 address stacks, the service consumer will use the IPv6 address provided by the service to initiate a service call, and if the IPv6 address call fails, if it also supports the IPv4 address stack, it is temporarily not supported to switch to IPv4 and then initiate a retry call; +(2) If the service consumer itself only supports IPv4 single-address stack, the service consumer will use the IPv4 address provided by the service to initiate service calls. + +### Only Register IPv4 address +If you only want to register IPv4 address.Config in application.properties as follows: +``` +spring.cloud.nacos.discovery.ip-type=IPv4 +``` + +### Only Register IPv6 address +If you only want to register IPv6 address.Config in application.properties as follows: +``` +spring.cloud.nacos.discovery.ip-type=IPv6 +``` ## Endpoint diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosDiscoveryProperties.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosDiscoveryProperties.java index 01b89ac07..38717767a 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosDiscoveryProperties.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosDiscoveryProperties.java @@ -80,6 +80,10 @@ public class NacosDiscoveryProperties { private static final Pattern PATTERN = Pattern.compile("-(\\w)"); + private static final String IPV4 = "IPv4"; + + private static final String IPV6 = "IPv6"; + /** * nacos discovery server address. */ @@ -165,11 +169,12 @@ public class NacosDiscoveryProperties { private String networkInterface = ""; /** - * choose IPv4 or IPv6,if you don't set it will choose IPv4. - * When IPv6 is chosen but no IPv6 can be found, system will automatically find IPv4 to ensure there is an - * available service address. + * choose IPv4 or IPv6 or both_IPv4_IPv6. if you don't set it will choose + * both_IPv4_IPv6. When IPv6 is chosen but no IPv6 can be found, system will + * automatically find IPv4 to ensure there is an available service address. If + * both_IPv4_IPv6 is set,both IPv4 and IPv6 will be register. */ - private String ipType = "IPv4"; + private String ipType; /** * The port your want to register for your service instance, needn't to set it if the @@ -257,16 +262,21 @@ public class NacosDiscoveryProperties { if (StringUtils.isEmpty(ip)) { // traversing network interfaces if didn't specify a interface if (StringUtils.isEmpty(networkInterface)) { - if ("IPv4".equalsIgnoreCase(ipType)) { + if (IPV4.equalsIgnoreCase(ipType)) { ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); } - else if ("IPv6".equalsIgnoreCase(ipType)) { + else if (IPV6.equalsIgnoreCase(ipType)) { ip = inetIPv6Utils.findIPv6Address(); if (StringUtils.isEmpty(ip)) { - log.warn("There is no available IPv6 found. Spring Cloud Alibaba will automatically find IPv4."); + log.warn( + "There is no available IPv6 found. Spring Cloud Alibaba will automatically find IPv4."); ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); } } + else if (ipType == null) { + ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); + metadata.put(IPV6, inetIPv6Utils.findIPv6Address()); + } else { throw new IllegalArgumentException( "please checking the type of IP " + ipType); diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/ribbon/NacosRule.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/ribbon/NacosRule.java index 32933d1f8..1cb257158 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/ribbon/NacosRule.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/ribbon/NacosRule.java @@ -16,13 +16,18 @@ package com.alibaba.cloud.nacos.ribbon; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; + import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; +import com.alibaba.cloud.nacos.util.InetIPv6Utils; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; @@ -33,6 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.commons.util.InetUtils; import org.springframework.util.CollectionUtils; /** @@ -40,17 +46,50 @@ import org.springframework.util.CollectionUtils; * instance. * * @author itmuch.com + * @author HH */ public class NacosRule extends AbstractLoadBalancerRule { private static final Logger LOGGER = LoggerFactory.getLogger(NacosRule.class); + private static final String IPV4_REGEX = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; + + private static final String IPV6_KEY = "IPv6"; + + private String ipv4; + + private String ipv6; + @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Autowired private NacosServiceManager nacosServiceManager; + @Autowired + private InetIPv6Utils inetIPv6Utils; + + @Autowired + private InetUtils inetUtils; + + @PostConstruct + public void init() { + String ip = nacosDiscoveryProperties.getIp(); + if (StringUtils.isNotEmpty(ip)) { + if (Pattern.matches(IPV4_REGEX, ip)) { + this.ipv4 = ip; + this.ipv6 = nacosDiscoveryProperties.getMetadata().get(IPV6_KEY); + } + else { + this.ipv6 = ip; + } + } + else { + this.ipv4 = getAppLocalIPv4Address(); + this.ipv6 = getAppLocalIPv6Address(); + } + } + @Override public Server choose(Object key) { try { @@ -65,6 +104,7 @@ public class NacosRule extends AbstractLoadBalancerRule { LOGGER.warn("no instance in service {}", name); return null; } + instances = filterInstanceByIpType(instances); List instancesToChoose = instances; if (StringUtils.isNotBlank(clusterName)) { @@ -83,6 +123,7 @@ public class NacosRule extends AbstractLoadBalancerRule { } Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose); + convertIPv4ToIPv6(instance); return new NacosServer(instance); } @@ -96,4 +137,53 @@ public class NacosRule extends AbstractLoadBalancerRule { public void initWithNiwsConfig(IClientConfig iClientConfig) { } + private String getAppLocalIPv4Address() { + return inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); + } + + private String getAppLocalIPv6Address() { + return inetIPv6Utils.findIPv6Address(); + } + + private List filterInstanceByIpType(List instances) { + if (StringUtils.isNotEmpty(this.ipv6)) { + List ipv6InstanceList = new ArrayList<>(); + for (Instance instance : instances) { + if (Pattern.matches(IPV4_REGEX, instance.getIp())) { + if (StringUtils.isNotEmpty(instance.getMetadata().get(IPV6_KEY))) { + ipv6InstanceList.add(instance); + } + } + else { + ipv6InstanceList.add(instance); + } + } + // provider has no IPv6,should use Ipv4. + if (ipv6InstanceList.size() == 0) { + return instances.stream() + .filter(instance -> Pattern.matches(IPV4_REGEX, instance.getIp())) + .collect(Collectors.toList()); + } + else { + return ipv6InstanceList; + } + } + return instances.stream() + .filter(instance -> Pattern.matches(IPV4_REGEX, instance.getIp())) + .collect(Collectors.toList()); + } + + /** + * There is two type Ip,using IPv6 should use IPv6 in metadata to replace IPv4 in IP + * field. + */ + private void convertIPv4ToIPv6(Instance instance) { + if (Pattern.matches(IPV4_REGEX, instance.getIp())) { + String ip = instance.getMetadata().get(IPV6_KEY); + if (StringUtils.isNotEmpty(ip)) { + instance.setIp(ip); + } + } + } + } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/InetIPv6Utils.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/InetIPv6Utils.java index acb7677e0..d69522332 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/InetIPv6Utils.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/InetIPv6Utils.java @@ -76,7 +76,7 @@ public class InetIPv6Utils implements Closeable { try { int lowest = Integer.MAX_VALUE; for (Enumeration nics = NetworkInterface - .getNetworkInterfaces(); nics.hasMoreElements();) { + .getNetworkInterfaces(); nics.hasMoreElements(); ) { NetworkInterface ifc = nics.nextElement(); if (ifc.isUp()) { log.trace("Testing interface:" + ifc.getDisplayName()); @@ -89,7 +89,7 @@ public class InetIPv6Utils implements Closeable { if (!ignoreInterface(ifc.getDisplayName())) { for (Enumeration addrs = ifc - .getInetAddresses(); addrs.hasMoreElements();) { + .getInetAddresses(); addrs.hasMoreElements(); ) { InetAddress inetAddress = addrs.nextElement(); if (inetAddress instanceof Inet6Address && !inetAddress.isLoopbackAddress() diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfigurationTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfigurationTest.java index da283dc37..3936774d5 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfigurationTest.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfigurationTest.java @@ -89,23 +89,27 @@ public class NacosDiscoveryClientConfigurationTest { public void testSpringCloudGatewayLocatorHeartBeatPublisherEnabled() { contextRunner .withPropertyValues("spring.cloud.gateway.discovery.locator.enabled=true") - .run(context -> assertThat(context).hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); + .run(context -> assertThat(context) + .hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); } @Test public void testZuulGatewayLocatorHeartBeatPublisherEnabled() { contextRunner - .withConfiguration(AutoConfigurations.of(ZuulProxyMarkerConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(ZuulProxyMarkerConfiguration.class)) .run(context -> assertThat(context) - .hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); + .hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); } @Test public void testZuulAndSpringCloudGatewayLocatorHeartBeatPublisherEnabled() { contextRunner .withPropertyValues("spring.cloud.gateway.discovery.locator.enabled=true") - .withConfiguration(AutoConfigurations.of(ZuulProxyMarkerConfiguration.class)) - .run(context -> assertThat(context).hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); + .withConfiguration( + AutoConfigurations.of(ZuulProxyMarkerConfiguration.class)) + .run(context -> assertThat(context) + .hasSingleBean(GatewayLocatorHeartBeatPublisher.class)); } }