SpringCloud LoadBalancer灰度策略实现

JAVA架构日记

共 3858字,需浏览 8分钟

 ·

2021-01-15 22:36

如何使用 Spring Cloud 2020 中重磅推荐的负载均衡器 Spring Cloud LoadBalancer (下文简称 SCL),如何扩展负载均衡策略?你将从本文中获取到答案

快速上手 SCL

  • 如果项目中想使用 SCL,则仅需要添加如下 maven 依赖即可

 org.springframework.cloud
 spring-cloud-starter-loadbalancer

  • SCL 是构建服务发现的基础上,由于目前 Spring Cloud Alibaba 并未兼容 SCL (具体兼容方案可以参考 pig[1]),当然你可以选择使用Eureka 测试。

  • 若将 RestTemplate 和 客户端负载均衡结合使用,在 bean 定义上增加 @LoadBalanced 注解即可.

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

个性化负载均衡策略

SCL 内置的负载均衡策略
  • 目前版本 (spring cloud 2020) 内置轮询、随机的负载均衡策略,默认轮询策略。 

  • 当然可以通过 LoadBalancerClient 注解,指定服务级别的负载均衡策略

@LoadBalancerClient(value = "demo-provider", configuration = RandomLoadbalancerConfig.class)
public class RandomLoadbalancerConfig {
 @Bean
 public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment,
   LoadBalancerClientFactory loadBalancerClientFactory)
 
{
  String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  return new RandomLoadBalancer(
    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
 }
}

自定义负载均衡策略

  • 通过上文可知,目前 SCL 支持的负载均衡策略相较于 Ribbon 还是较少,需要开发者自行实现,好在 SCL 提供了便捷的 API 方便扩展使用。这里演示自定义一个基于注册中心元数据的灰度负载均衡策略。

  • 定义灰度负载均衡策略

@Slf4j
public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer {

 private ObjectProvider serviceInstanceListSupplierProvider;

 private String serviceId;

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

 Response getInstanceResponse(List instances, Request request) {

  // 注册中心无可用实例 抛出异常
  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();
  }

  // 遍历可以实例元数据,若匹配则返回此实例
  for (ServiceInstance instance : instances) {
   NacosServiceInstance nacosInstance = (NacosServiceInstance) instance;
   Map 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);
   }
  }
  // 降级策略,使用轮询策略
  return super.choose(request).block();
 }
}
  • 针对客户端注入灰度负载均衡策略
@LoadBalancerClient(value = "demo-provider", configuration = GrayRoundLoadbalancerConfig.class)
  • 服务实例定义版本号
  • 请求携带版本号,测试使用
curl --location --request GET 'http://localhost:6060/req?key=b' \
--header 'VERSION: b'

优化负载均衡策略注入

  • 如上文所述,所有的个性化负载策略都需要手动通过 LoadBalancerClient 注入非常的不方便。我们可以参考 LoadBalancerClients 的批量注入逻辑构造自己的 BeanRegistrar
public class GrayLoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

 @Override
 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  Field[] fields = ReflectUtil.getFields(ServiceNameConstants.class);

  // 遍历服务名称,注入支持灰度策略的负载均衡器
  for (Field field : fields) {
   Object fieldValue = ReflectUtil.getFieldValue(ServiceNameConstants.classfield);
   registerClientConfiguration(registry, fieldValue, GrayLoadBalancerClientConfiguration.class);
  }
 }
}

参考资料

[1]

兼容Spring Cloud 2020 方案: https://gitee.com/log4j/pig

往期推荐

伊尔福德站正式通车!Spring Cloud 2020 发布

「Spring Boot 新特性」一键构建Docker镜像

「Spring Boot 新特性」 jar 大小自动瘦身

「SpringBoot 新特性」启动耗时详细监控

Spring Boot 2.4.0 正式发布,全面拥抱云原生


浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报