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]
- 从中随机选择一个实例