Gateway实现根据服务注册时间路由的简单实现

648 阅读1分钟

环境和版本

<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>

实现思路

  1. 服务注册时,在注册信息的metadata中增加注册时间。
  2. 重写网关的负载均衡策略,获取对应服务的所有实例拿到metadata中的注册时间,过滤出指定时间内的实例,从其中选中后然后发送请求。
  3. 使用@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。