How to use the Spring Cloud LoadBalancer (SCL) recommended in Spring Cloud 2020, and how to extend the load balancing policy? You will find the answer in this article

Get up and running with SCL

  • If you want to use SCL in your project, you simply need to add the following Maven dependencies
<dependency>

 <groupId>org.springframework.cloud</groupId>

 <artifactId>spring-cloud-starter-loadbalancer</artifactId>

</dependency>

Copy the code
  • SCL is built on the basis of service discovery. Since Spring Cloud Alibaba is not currently compatible with SCL (please refer to Pig [1] for specific compatibility scheme), of course, you can choose to use Eureka test.

  • To use RestTemplate with client-side load balancing, add the @loadBalanced annotation to the bean definition.

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

    return new RestTemplate();

}

Copy the code

Personalized load balancing policies

Load balancing policies built into SCL
  • The current version (Spring Cloud 2020) has polling and random load balancing policies built in. The default polling policy.

  • You can, of course, specify a service-level load balancing policy via the LoadBalancerClient annotation

@LoadBalancerClient(value = "demo-provider", configuration = RandomLoadbalancerConfig.class)

Copy the code
public class RandomLoadbalancerConfig {

 @Bean

 public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,

   LoadBalancerClientFactory loadBalancerClientFactory)
 
{

  String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

  return new RandomLoadBalancer(

    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);

 }

}

Copy the code

User-defined load balancing policies

  • As we can see from the above, SCL currently supports fewer load balancing strategies than the Ribbon, which requires developers to implement by themselves. Fortunately, SCL provides a convenient API for extension. Here we demonstrate a custom gray scale load balancing strategy based on registry metadata.

  • Define a grayscale load balancing policy

@Slf4j

public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer {



 private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;



 private String serviceId;



 @Override

 public Mono<Response<ServiceInstance>> choose(Request request) {

  ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider

    .getIfAvailable(NoopServiceInstanceListSupplier::new);

  return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(serviceInstances, request));

 }



 Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {



  // There are no available instances of the registry throwing an exception

  if (CollUtil.isEmpty(instances)) {

   log.warn("No instance available {}", serviceId);

   return new EmptyResponse();

  }



  DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();

  RequestData clientRequest = (RequestData) requestContext.getClientRequest();

  HttpHeaders headers = clientRequest.getHeaders();



  String reqVersion = headers.getFirst(CommonConstants.VERSION);

  if (StrUtil.isBlank(reqVersion)) {

   return super.choose(request).block();

  }



  // Iterate over the instance metadata and return the instance if it matches

  for (ServiceInstance instance : instances) {

   NacosServiceInstance nacosInstance = (NacosServiceInstance) instance;

   Map<String, String> metadata = nacosInstance.getMetadata();

   String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);

   if (reqVersion.equalsIgnoreCase(targetVersion)) {

    log.debug("gray requst match success :{} {}", reqVersion, nacosInstance);

    return new DefaultResponse(nacosInstance);

   }

  }

  // Demote policy, use polling policy

  return super.choose(request).block();

 }

}

Copy the code
  • The grayscale load balancing policy is injected into the client
@LoadBalancerClient(value = "demo-provider", configuration = GrayRoundLoadbalancerConfig.class)

Copy the code
  • Service instance defines the version number

  • Request to carry version number for test use
curl --location --request GET 'http://localhost:6060/req? key=b' \

--header 'VERSION: b'

Copy the code

Optimized load balancing policy injection

  • As mentioned above, all personalized load policies need to be passed manuallyLoadBalancerClientInjection is very inconvenient. We can refer toLoadBalancerClientsConstruct your own BeanRegistrar with bulk injection logic

public class GrayLoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {



 @Override

 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

  Field[] fields = ReflectUtil.getFields(ServiceNameConstants.class);



  // Iterate over the service name and inject a load balancer that supports grayscale strategy

  for (Field field : fields) {

   Object fieldValue = ReflectUtil.getFieldValue(ServiceNameConstants.class.field);

   registerClientConfiguration(registry, fieldValue, GrayLoadBalancerClientConfiguration.class);

  }

 }

}

Copy the code

The resources

[1]

Compatible with Spring Cloud 2020 plan: https://gitee.com/log4j/pig