环境和版本
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-boot.version>2.4.11</spring-boot.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
<security.version>2.2.4.RELEASE</security.version>
<knife4j.version>3.0.3</knife4j.version>
<springfox.version>3.0.0</springfox.version>
<mybatis-plus.version>3.4.2</mybatis-plus.version>
实现思路
- 服务注册时,在注册信息的metadata中增加注册时间。
- 重写网关的负载均衡策略,获取对应服务的所有实例拿到metadata中的注册时间,过滤出指定时间内的实例,从其中选中后然后发送请求。
- 使用
@LoadBalancerClients(defaultConfiguration =CustomerLoadBalancerConfiguration.class)注册自定义的负载均衡配置类。
具体代码
1.在服务注册信息中追加注册时间
/**
* 在注册信息的元数据增加注册时间,用于网关根据注册时间路由服务
* @return
*/
@Bean
@ConditionalOnNacosDiscoveryEnabled
public NacosDiscoveryProperties nacosDiscoveryProperties(){
HashMap<String, String> metaDataMap = new HashMap<>();
metaDataMap.put("startup-time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
NacosDiscoveryProperties nacosDiscoveryProperties = new NacosDiscoveryProperties();
nacosDiscoveryProperties.setMetadata(metaDataMap);
return nacosDiscoveryProperties;
}
}
上述代码增加后在配置文件里继续增加meatadata也是可以的,最后会合并。
cloud:
nacos:
server-addr: ${nacos.addr}
discovery:
register-enabled: true
namespace: ${nacos.namespace}
group: ${nacos.group}
# 这里的metadata会和上面代码中的合并到一起
metadata:
name: test
2.仿写RoundRobinLoadBalancer实现自己的负载均衡策略
下面其实就是复制了RoundRobinLoadBalancer,然后增加了自己的逻辑,当所有服务注册时间都超过了5分钟,还是要使用轮询的。
public class RegisterTimeServiceLoadbalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RegisterTimeServiceLoadbalancer.class);
final AtomicInteger position = new AtomicInteger(new Random().nextInt(1000));
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public RegisterTimeServiceLoadbalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this.serviceInstanceListSupplierProvider=serviceInstanceListSupplierProvider;
this.serviceId=serviceId;
}
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
log.warn("No Service found,ServiceId:"+serviceId);
return new EmptyResponse();
}
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
// 如果能获取到实例那么就使用,否则还是轮询
ServiceInstance instanceByRegTime = getInstanceByRegTime(instances, pos);
if (instanceByRegTime!=null) instance=instanceByRegTime;
return new DefaultResponse(instance);
}
/**
* 这里根据注册时间筛选出5分钟内新注册到注册中心的实例
* @param instances
* @param pos
* @return
*/
private ServiceInstance getInstanceByRegTime(List<ServiceInstance> instances, int pos) {
ServiceInstance serviceInstance=null;
List<ServiceInstance> newRegisterInstances = instances.stream().filter(e -> {
String registerTimeStr = e.getMetadata().get("startup-time");
LocalDateTime dateTime = LocalDateTime.parse(registerTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return LocalDateTime.now().minusMinutes(5).isBefore(dateTime);
}).collect(Collectors.toList());
if (newRegisterInstances!=null&&!newRegisterInstances.isEmpty()){
serviceInstance = newRegisterInstances.get(pos % newRegisterInstances.size());
}
return serviceInstance;
}
}
增加配置类
public class CustomerLoadBalancerConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RegisterTimeServiceLoadbalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
Spring官网中有明确的要求:自定义的LoadBalancer配置类不能被Spring扫描到。因为每个LoadBalancer都是根据service隔离的,类似子父类容器。
3.使用注解将自己的配置类注册为默认的配置
在网关服务中增加@LoadBalancerClients(defaultConfiguration = CustomerLoadBalancerConfiguration.class)注解,将配置类注册为默认的config。