Bootstrap

Gateway实现动态路由

       本文主要聊聊Spring Cloud Gateway(以下简称gateway,使用的版本是2.2.1.RELEASE),结合Nacos的注册中心,实现根据微服务名,自动路由到对应的微服务。在gateway的官网上,可以看到如下描述:

 大致意思是,通过如下配置,可以实现自动根据服务发现为每一个服务创建了一个路由router,
这个router将以服务名开头的请求路径转发到对应的服务:

spring.cloud.gateway.discovery.locator.enabled=true

并且在很多博客中,也可以发现类似的说法:

spring.cloud.gateway.discovery.locator.enabled为true,表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,
这个router将以服务名开头的请求路径转发到对应的服务。

       但是,我在测试中发现该配置并不起作用。在GitHub中也找到相关问题:

 

      于是,找了很多资料去佐证,发现很少有人提及这个问题,但是并不是毫无进展,起码知道了spring.cloud.gateway.discovery.locator.enabled配置生效的地方是GatewayDiscoveryClientAutoConfiguration的静态内部类:

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnProperty(
        value = {"spring.cloud.discovery.reactive.enabled"},
        matchIfMissing = true
    )
    public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {
        public ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration() {
        }

        @Bean
        @ConditionalOnProperty(
            name = {"spring.cloud.gateway.discovery.locator.enabled"}
        )
        public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
            return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
        }
    }

在这里,创建了DiscoveryClientRouteDefinitionLocator对象,DiscoveryClientRouteDefinitionLocator实现了RouteDefinitionLocator接口,该接口只有获取路由定义的方法:

public interface RouteDefinitionLocator {
    Flux<RouteDefinition> getRouteDefinitions();
}

DiscoveryClientRouteDefinitionLocator的实现,主要是对serviceInstances的操作:

而serviceInstances来源于构造函数:

    public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
        this(discoveryClient.getClass().getSimpleName(), properties);
        this.serviceInstances = discoveryClient.getServices().flatMap((service) -> {
            return discoveryClient.getInstances(service).collectList();
        });
    }

 discoveryClient的实现类是ReactiveCompositeDiscoveryClient:

 该方法确实实现了基于服务的路由创建:

 但是ReactiveCompositeDiscoveryClient实现的接口是ReactiveDiscoveryClient,和NacosDiscoveryClient毫无关联,所谓它无法获取服务的注册列表,进而无法创建有效的路由。

既然配置不行,就只能自己实现RouteDefinitionLocator接口了:

@Component
public class MyRouteDefinitionLocator implements RouteDefinitionLocator {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Value("${spring.application.name}")
    private String gatewayName;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 服务发现
        List<String> serviceList = discoveryClient.getServices();
        System.out.println("serviceList = "+serviceList);
        // 只有网关服务,直接返回
        if (serviceList.size() < 2){
            return Flux.empty();
        }
        // 服务的个数
        int serviceNum = serviceList.size();
        if (serviceList.contains(gatewayName)){
            // 排除网关服务
            serviceNum--;

        }
        RouteDefinition[] routeDefinitions = new RouteDefinition[serviceNum];
        int count = 0;
        for (String service : serviceList) {
            if (service.equalsIgnoreCase(gatewayName)){
                continue;
            }
            RouteDefinition definition = new RouteDefinition();
            definition.setId(service);
            // 负载均衡
            definition.setUri(URI.create("lb://"+service));
            // 根据微服务名,进行路径匹配
            definition.setPredicates(Collections.singletonList(new PredicateDefinition("Path=/"+service+"/**")));
           // 去掉微服务名
            definition.setFilters(Collections.singletonList(new FilterDefinition("StripPrefix=1")));
            routeDefinitions[count++] = definition;
        }
        return Flux.just(routeDefinitions);
    }
}

     通过RouteDefinitionLocator接口,实现全量更新路由信息,达到根据微服务名动态路由到对应的微服务的目的。比如调用:

http://localhost:8888/feign-consumer/feign/test?msg=test123

     这里的端口是网关的端口,feign-consumer是注册到nacos的微服务名,则会根据微服务名路由到feign-consumer微服务。

示例代码地址:GitHub - qiuxinfa/cloud-alibaba

;