Spring Cloud 中自定义 FeignClient 负载均衡算法:加权随机示例





Spring Cloud 中自定义 FeignClient 负载均衡算法:加权随机示例


Spring Cloud 中自定义 FeignClient 负载均衡算法:加权随机示例

🧩 场景描述

本文将介绍如何在 Spring Cloud 2023+ 中,将 RestTemplate 替换为 FeignClient,并使用自定义的 加权随机负载均衡算法 实现服务实例选择。

1️⃣ 使用 FeignClient

@FeignClient(name = "my-service")
public interface MyServiceClient {
    @GetMapping("/api/hello")
    String sayHello();
}

2️⃣ 实现 WeightedLoadBalancer

public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {

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

    public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provider, String serviceId) {
        this.serviceInstanceListSupplierProvider = provider;
        this.serviceId = serviceId;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        return serviceInstanceListSupplierProvider.getIfAvailable()
            .get()
            .next()
            .map(serviceInstances -> {
                if (serviceInstances.isEmpty()) {
                    return new EmptyResponse();
                }
                List<ServiceInstance> instances = new ArrayList<>();
                for (ServiceInstance instance : serviceInstances) {
                    String weightStr = instance.getMetadata().getOrDefault("weight", "1");
                    int weight = Integer.parseInt(weightStr);
                    for (int i = 0; i < weight; i++) {
                        instances.add(instance); // 添加多次以表示权重
                    }
                }
                int randomIndex = ThreadLocalRandom.current().nextInt(instances.size());
                return new DefaultResponse(instances.get(randomIndex));
            });
    }
}

3️⃣ 注册自定义负载均衡器

@LoadBalancerClient(name = "my-service", configuration = MyServiceLoadBalancerConfig.class)
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
public class MyServiceLoadBalancerConfig {
    @Bean
    public ReactorServiceInstanceLoadBalancer weightedLoadBalancer(
        Environment environment, LoadBalancerClientFactory factory) {
        String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new WeightedLoadBalancer(
            factory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
            serviceId
        );
    }
}

4️⃣ 服务注册权重配置

spring:
  application:
    name: my-service
eureka:
  instance:
    metadata-map:
      weight: 3

5️⃣ 源码逐句解释

public Mono<Response<ServiceInstance>> choose(Request request) {
定义一个选择服务实例的方法,返回的是响应式的 Mono。
    return serviceInstanceListSupplierProvider.getIfAvailable()
从 Spring 容器中获取服务实例提供器。
            .get()
获取实际的 ServiceInstanceListSupplier 实例。
            .next()
获取下一个服务实例列表。
            .map(serviceInstances -> {
                if (serviceInstances.isEmpty()) {
                    return new EmptyResponse();
                }
如果没有可用实例,返回空响应。
                List<ServiceInstance> instances = new ArrayList<>();
                for (ServiceInstance instance : serviceInstances) {
                    String weightStr = instance.getMetadata().getOrDefault("weight", "1");
                    int weight = Integer.parseInt(weightStr);
                    for (int i = 0; i < weight; i++) {
                        instances.add(instance);
                    }
                }
根据 metadata 的权重重复添加服务实例,实现“加权列表”。
                int randomIndex = ThreadLocalRandom.current().nextInt(instances.size());
                return new DefaultResponse(instances.get(randomIndex));
            });
}
从加权列表中随机选择一个服务实例作为响应。

📊 图解:加权负载均衡过程

如下图所示(稍后你将看到):

  • 实例 A 权重 1,B 权重 2,C 权重 3
  • 创建加权列表:[A, B, B, C, C, C]
  • 从中随机选择一个实例

Leave a Comment

Your email address will not be published. Required fields are marked *