最近在支援项目时,项目组需要将后端服务部署为多个实例,且没有集成服务注册中心、反向代理、K8S等,如此提出需要客户端OpenFeign、RestTemplate等支持手动配置服务端地址列表,并支持在服务端实例间负载均衡,经过调研后给出如下基于Spring Cloud LoadBalancer的集成方案。
OpenFeign、RestTemplate均是通过url对服务发起调用,具体url的负载均衡则是由Spring Cloud LoadBalancer负责,所以需要在LoadBalancer上做文章,查询相关文档后给出如下思路:
@LoadBalancerClient(name, configuration)
注解为不同服务标识指定相应的配置类
@FeignClient.name
,或 RestTemplate调用url http://serviceId/path
中的serviceId
ServiceInstanceListSupplier
ServiceInstanceListSupplier
中返回该服务标识手动配置的服务端地址列表接下来给出核心实现代码。
首先,自定义配置属性如下:
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 负载均衡静态配置
*
* @author luohq
* @date 2024-04-25 9:35
*/
@ConfigurationProperties(prefix = LoadBalancerStaticProps.PREFIX)
public class LoadBalancerStaticProps {
public static final String PREFIX = "spring.cloud.loadbalancer.clients";
/**
* 客户端ServiceId到静态地址列表的映射
*/
private Map<String, List<String>> staticUris = new HashMap<>();
public Map<String, List<String>> getStaticUris() {
return staticUris;
}
public void setStaticUris(Map<String, List<String>> staticUris) {
this.staticUris = staticUris;
}
@Override
public String toString() {
return "LoadBalancerStaticProps{" +
"staticUris=" + staticUris +
'}';
}
}
后续可通过如下示例配置,手动指定不同服务标识对应的服务端地址列表:
spring:
cloud:
loadbalancer:
clients:
# 配置负载均衡静态地址
static-uris:
# app-rbac服务的静态地址列表
app-rbac:
- http://localhost:8081
- http://localhost:8082
# app-atom服务的静态地址列表
app-atom:
- http://localhost:9081
- http://localhost:9082
StaticServiceInstanceListSuppler实现了ServiceInstanceListSupplier接口,
这个接口是Spring Cloud LoadBalancer的一部分,是一个通用的客户端负载均衡器。
StaticServiceInstanceListSuppler支持自定义serviceId对应的服务地址列表serviceStaticUriList,
同时提供create方法,支持从前文定义的配置属性LoadBalancerStaticProps 中(即从配置文件中)获取serviceId对应的服务地址列表。
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 静态服务地址提供器
*
* @author luohq
* @date 2024-04-25
*/
public class StaticServiceInstanceListSuppler implements ServiceInstanceListSupplier {
/**
* 服务标识
*/
private final String serviceId;
/**
* 服务地址列表
*/
private final List<String> serviceStaticUriList;
public StaticServiceInstanceListSuppler(String serviceId, List<String> serviceStaticUriList) {
Assert.notNull(serviceId, "serviceId may not be null");
Assert.notEmpty(serviceStaticUriList, "serviceStaticUriList may not be null");
this.serviceId = serviceId;
this.serviceStaticUriList = serviceStaticUriList;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
List<ServiceInstance> defaultServiceInstanceList = IntStream.range(0, serviceStaticUriList.size())
.mapToObj(i -> {
DefaultServiceInstance defaultServiceInstance = new DefaultServiceInstance();
defaultServiceInstance.setServiceId(this.serviceId);
defaultServiceInstance.setInstanceId(this.serviceId + i);
defaultServiceInstance.setUri(URI.create(serviceStaticUriList.get(i)));
return defaultServiceInstance;
})
.collect(Collectors.toList());
return Flux.just(defaultServiceInstanceList);
}
public static StaticServiceInstanceListSuppler create(String serviceId, LoadBalancerStaticProps loadBalancerStaticProps) {
return new StaticServiceInstanceListSuppler(serviceId, loadBalancerStaticProps.getStaticUris().get(serviceId));
}
}
如下实现即对应serviceId=app-rbac
服务的专属配置类,
不同的serviceId可再单独定义相应的配置类。
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* RBAC客户端负载均衡静态配置
*
* @author luohq
* @date 2024-04-25 9:35
*/
public class RbacClientLoadBalancerStaticConfiguration {
public static final String SERVICE_ID = "app-rbac";
@Bean
@Primary
public ServiceInstanceListSupplier serviceInstanceListSupplier(LoadBalancerStaticProps loadBalancerStaticProps) {
return StaticServiceInstanceListSuppler.create(SERVICE_ID, loadBalancerStaticProps);
}
}
如果项目中使用的是OpenFeign,以下配置已足够:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Configuration;
/**
* RBAC客户端负载均衡静态配置
*
* @author luohq
* @date 2024-04-25 9:35
*/
@Configuration
@LoadBalancerClients({
//自定义RBAC服务配置
@LoadBalancerClient(
name = RbacClientLoadBalancerStaticConfiguration.SERVICE_ID,
configuration = RbacClientLoadBalancerStaticConfiguration.class
)
})
@EnableConfigurationProperties({LoadBalancerStaticProps.class})
public class LoadBalancerStaticConfiguration2 {
}
如果项目中使用的是RestTemplate,那么还需要通过@LoadBalanced注解设置RestTemplate支持负载均衡,
具体配置代码如下:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RBAC客户端负载均衡静态配置
*
* @author luohq
* @date 2024-04-25 9:35
*/
@Configuration
@LoadBalancerClients({
//自定义RBAC服务配置
@LoadBalancerClient(
name = RbacClientLoadBalancerStaticConfiguration.SERVICE_ID,
configuration = RbacClientLoadBalancerStaticConfiguration.class
)
})
@EnableConfigurationProperties({LoadBalancerStaticProps.class})
public class LoadBalancerStaticConfiguration2 {
/**
* 通过@LoadBalanced注解开启RestTemplate负载均衡能力
*/
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Spring Cloud LoadBalancer默认使用的是RoundRobinLoadBalancer,可重点关注RoundRobinLoadBalancer中获取服务实例的代码。
参考:
https://spring.io/guides/gs/spring-cloud-loadbalancer
https://juejin.cn/post/7266315019294490661
https://docs.spring.io/spring-cloud-commons/docs/3.1.8/reference/html/#spring-cloud-loadbalancer