Skip to content

Commit

Permalink
feature 2023.x: Expand NacosLoadBalancer to conveniently support cust…
Browse files Browse the repository at this point in the history
…om service list filtering and load balancing algorithms (#3794)
  • Loading branch information
zhangbinhub committed Jul 16, 2024
1 parent 3445916 commit e9ba0af
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed 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
*
* https://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.nacos.loadbalancer;

import java.util.List;

import com.alibaba.cloud.nacos.balancer.NacosBalancer;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.core.Ordered;

/**
* This is a default implementation of load balancing algorithm.
* use {@link com.alibaba.cloud.nacos.balancer.NacosBalancer}
*
* @author <a href="mailto:zhangbin1010@qq.com">zhangbinhub</a>
*/
public class DefaultLoadBalancerAlgorithm implements LoadBalancerAlgorithm {
@Override
public String getServiceId() {
return LoadBalancerAlgorithm.DEFAULT_SERVICE_ID;
}

@Override
public ServiceInstance getInstance(Request<?> request, List<ServiceInstance> serviceInstances) {
return NacosBalancer.getHostByRandomWeight3(serviceInstances);
}

@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed 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
*
* https://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.nacos.loadbalancer;

import java.util.List;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.core.Ordered;

/**
* Load Balancer algorithm interface.
* When expanding the load balancing algorithm, implement this interface and register it as a bean.
*
* @author <a href="mailto:zhangbin1010@qq.com">zhangbinhub</a>
*/
public interface LoadBalancerAlgorithm extends Ordered {
/**
* default service id.
*/
String DEFAULT_SERVICE_ID = "defaultServiceId";

String getServiceId();

ServiceInstance getInstance(Request<?> request, List<ServiceInstance> serviceInstances);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
Expand All @@ -32,5 +33,8 @@
@ConditionalOnNacosDiscoveryEnabled
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class LoadBalancerNacosAutoConfiguration {

@Bean
public LoadBalancerAlgorithm defaultLoadBalancerAlgorithm() {
return new DefaultLoadBalancerAlgorithm();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.balancer.NacosBalancer;
import com.alibaba.cloud.nacos.util.InetIPv6Utils;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import jakarta.annotation.PostConstruct;
Expand All @@ -32,7 +32,6 @@
import reactor.core.publisher.Mono;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
Expand All @@ -55,7 +54,7 @@ public class NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private final String serviceId;

private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

private final NacosDiscoveryProperties nacosDiscoveryProperties;

Expand All @@ -67,9 +66,11 @@ public class NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
*/
public static String ipv6;

@Autowired
private InetIPv6Utils inetIPv6Utils;
private final InetIPv6Utils inetIPv6Utils;

private final List<ServiceInstanceFilter> serviceInstanceFilters;

private final Map<String, LoadBalancerAlgorithm> loadBalancerAlgorithmMap;

@PostConstruct
public void init() {
Expand All @@ -96,7 +97,7 @@ private List<ServiceInstance> filterInstanceByIpType(List<ServiceInstance> insta
}
}
// Provider has no IPv6, should use IPv4.
if (ipv6InstanceList.size() == 0) {
if (ipv6InstanceList.isEmpty()) {
return instances.stream()
.filter(instance -> Pattern.matches(IPV4_REGEX, instance.getHost()))
.collect(Collectors.toList());
Expand All @@ -112,23 +113,28 @@ private List<ServiceInstance> filterInstanceByIpType(List<ServiceInstance> insta

public NacosLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties, InetIPv6Utils inetIPv6Utils,
List<ServiceInstanceFilter> serviceInstanceFilters,
Map<String, LoadBalancerAlgorithm> loadBalancerAlgorithmMap) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.inetIPv6Utils = inetIPv6Utils;
this.serviceInstanceFilters = serviceInstanceFilters;
this.loadBalancerAlgorithmMap = loadBalancerAlgorithmMap;
}

@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(this::getInstanceResponse);
return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(request, serviceInstances));
}

private Response<ServiceInstance> getInstanceResponse(
private Response<ServiceInstance> getInstanceResponse(Request<?> request,
List<ServiceInstance> serviceInstances) {
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
log.warn("No servers available for service: {}", this.serviceId);
return new EmptyResponse();
}

Expand All @@ -154,8 +160,20 @@ private Response<ServiceInstance> getInstanceResponse(
}
instancesToChoose = this.filterInstanceByIpType(instancesToChoose);

ServiceInstance instance = NacosBalancer
.getHostByRandomWeight3(instancesToChoose);
// Filter the service list sequentially based on the order number
for (ServiceInstanceFilter filter : serviceInstanceFilters) {
instancesToChoose = filter.filterInstance(request, instancesToChoose);
}

ServiceInstance instance;
// Find the corresponding load balancing algorithm through the service ID and select the final service instance
if (loadBalancerAlgorithmMap.containsKey(serviceId)) {
instance = loadBalancerAlgorithmMap.get(serviceId).getInstance(request, instancesToChoose);
}
else {
instance = loadBalancerAlgorithmMap.get(LoadBalancerAlgorithm.DEFAULT_SERVICE_ID)
.getInstance(request, instancesToChoose);
}

return new DefaultResponse(instance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

package com.alibaba.cloud.nacos.loadbalancer;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.util.InetIPv6Utils;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand Down Expand Up @@ -47,6 +52,7 @@
* @since 2021.1
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnLoadBalancerNacos
@ConditionalOnDiscoveryEnabled
public class NacosLoadBalancerClientConfiguration {

Expand All @@ -56,12 +62,22 @@ public class NacosLoadBalancerClientConfiguration {
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory,
NacosDiscoveryProperties nacosDiscoveryProperties) {
NacosDiscoveryProperties nacosDiscoveryProperties,
InetIPv6Utils inetIPv6Utils,
List<ServiceInstanceFilter> serviceInstanceFilters,
List<LoadBalancerAlgorithm> loadBalancerAlgorithms) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
Map<String, LoadBalancerAlgorithm> loadBalancerAlgorithmMap = new HashMap<>();
loadBalancerAlgorithms.forEach(loadBalancerAlgorithm -> {
if (!loadBalancerAlgorithmMap.containsKey(loadBalancerAlgorithm.getServiceId())) {
loadBalancerAlgorithmMap.put(loadBalancerAlgorithm.getServiceId(), loadBalancerAlgorithm);
}
});
return new NacosLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class),
name, nacosDiscoveryProperties);
name, nacosDiscoveryProperties, inetIPv6Utils,
serviceInstanceFilters, loadBalancerAlgorithmMap);
}

@Configuration(proxyBeanMethods = false)
Expand Down Expand Up @@ -115,7 +131,5 @@ public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceL
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient()
.withZonePreference().build(context);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed 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
*
* https://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.nacos.loadbalancer;

import java.util.List;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.core.Ordered;

/**
* Service Instance Filter interface.
* When custom service instance list filter, implement this interface and register it as a bean.
*
* @author <a href="mailto:zhangbin1010@qq.com">zhangbinhub</a>
*/
public interface ServiceInstanceFilter extends Ordered {
List<ServiceInstance> filterInstance(Request<?> request, List<ServiceInstance> serviceInstances);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.nacos.discovery;

import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration;
import com.alibaba.cloud.nacos.loadbalancer.LoadBalancerAlgorithm;
import com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration;
import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration;
import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration;
import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration;
import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author <a href="mailto:zhangbin1010@qq.com">zhangbinhub</a>
**/
public class NacosDiscoveryLoadBalancerConfigurationTest {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
AutoServiceRegistrationConfiguration.class,
NacosServiceRegistryAutoConfiguration.class,
UtilAutoConfiguration.class,
UtilIPv6AutoConfiguration.class,
NacosServiceAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class,
NacosDiscoveryClientConfiguration.class,
LoadBalancerAutoConfiguration.class, this.getClass()));

@Test
public void testNacosLoadBalancerEnabled() {
contextRunner.withPropertyValues("spring.cloud.loadbalancer.nacos.enabled=true")
.withConfiguration(AutoConfigurations.of(
LoadBalancerNacosAutoConfiguration.class,
NacosLoadBalancerClientConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LoadBalancerAlgorithm.class);
assertThat(context).hasBean("nacosLoadBalancer");
});
}

@Test
public void testNacosLoadBalancerDisabled() {
contextRunner.withPropertyValues("spring.cloud.loadbalancer.nacos.enabled=false")
.withConfiguration(AutoConfigurations.of(
LoadBalancerNacosAutoConfiguration.class,
NacosLoadBalancerClientConfiguration.class))
.run(context -> {
assertThat(context).doesNotHaveBean(LoadBalancerAlgorithm.class);
assertThat(context).doesNotHaveBean("nacosLoadBalancer");
});
}

}

0 comments on commit e9ba0af

Please sign in to comment.