本文主要聊聊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接口,实现全量更新路由信息,达到根据微服务名动态路由到对应的微服务的目的。比如调用:
这里的端口是网关的端口,feign-consumer是注册到nacos的微服务名,则会根据微服务名路由到feign-consumer微服务。