Spring Cloud Gateway系列【8】基于注册中心Nacos的动态路由案例及加载执行流程源码分析

文章目录

    • 前言
  • 实现案例
      1. 引入Nacos
      1. 网关添加配置
      1. 测试
  • 执行流程
      1. 动态加载路由
      1. 执行过滤器

前言

在之前的案例中,我们的路由都是写在配置文件中的,在微服务架构中,后台有很多个,如果每一个都需要配置,那么肯定是不现实的,所以Spring Cloud Gateway提供了基于注册中心服务发现机制的动态路由。

Spring Cloud Gateway支持与Eureka、Nacos、Consul等整合开发,根据service ld自动从注册中心获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例时不用修改Gateway的路由配置。

实现案例

1. 引入Nacos

参照这个文档Nacos系列(3)-SpringCloud集成Nacos,网关及后台服务都注册到Nacos中。
 
如果spring cloud 采用2020 版本,需要引入loadbalancer 依赖。

 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

否则会报错:

Parameter 0 of method loadBalancerWebClientBuilderBeanPostProcessor in org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration required a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' in your configuration.

2. 网关添加配置

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      enabled: true
      metrics:
        # 开启 GatewayMetricsFilter
        enabled: true
      discovery:
        locator:
          # 开启服务发现动态路由
          enabled: true
          # 是否将服务名称小写
          lower-case-service-id: true

3. 测试

通过以下方式访问,就可以通过服务名,转发到具体的后台应用。

# 网关地址/服务注册名/目标请求路径
http://localhost/app-service001/app1/test

 

执行流程

1. 动态加载路由

1.1 DiscoveryClientRouteDefinitionLocator
在之前的案例中,我们分析过,路由信息会被加载到RouteDefinitionLocator中,YML配置的路由是PropertiesRouteDefinitionLocator来加载的。

基于服务发现时,使用的是DiscoveryClientRouteDefinitionLocator

DiscoveryClientRouteDefinitionLocator的成员属性如下所示:

    private final DiscoveryLocatorProperties properties;
    private final String routeIdPrefix;
    private final SimpleEvaluationContext evalCtxt;
    private Flux<List<ServiceInstance>> serviceInstances;

DiscoveryLocatorProperties 是针对服务发现的相关配置,
 
routeIdPrefix表示路由的前缀,没有设置时,默认为:ReactiveCompositeDiscoveryClient_

SimpleEvaluationContext 是SPEL表达式的Context。

serviceInstances就存放了动态的路由规则。

1.2 初始化DiscoveryClientRouteDefinitionLocator

通过构造函数进行初始化,

    public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
   
     
    	// 1. 属性初始化,routeIdPrefix、SimpleEvaluationContext 
        this(discoveryClient.getClass().getSimpleName(), properties);
        // 2. 调用服务发现客户端,查询动态的路由信息。
        this.serviceInstances = discoveryClient.getServices().flatMap((service) -> {
   
     
            return discoveryClient.getInstances(service).collectList();
        });
    }

构造函数需要ReactiveDiscoveryClientDiscoveryLocatorProperties参数,ReactiveDiscoveryClient是一个服务发现客户端,是Spring cloud提供的协议接口,因为我们引入的是Nacos,所以这里的客户端为NacosReactiveDiscoveryClient

1.3 获取RouteDefinitions
DiscoveryClientRouteDefinitionLocator通过调用 DiscoveryClient 获取在注册中心的服务列表,生成对应的 RouteDefinition 数组,调用的是getRouteDefinitions方法,最终在注册中心的服务,就被网关获取到路由信息了。
 

2. 执行过滤器

2.1 路由信息

获取到动态路由后,就是方法执行了。

首先可以看到,动态路由中,有一个Path类型的断言,其值为/app-service001/**,那么只要是访问路径以注册服务名开头,则就会匹配这个路由。
 
还可以看到,这个路由还有一个网关过滤器RewritePath。

2.2 进入网关过滤器RewritePathGatewayFilterFactory

请求经过Web 处理=》断言通过以后,就到达了RewritePathGatewayFilterFactory网关过滤器中,主要是去掉访问路径的服务名,并设置到Request中。

				
 				ServerHttpRequest req = exchange.getRequest();
 				// ServerWebExchange添加属性,gatewayOriginalRequestUrl=》http://localhost/app-service001/app1/test
                ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.getURI());
                // 原始路径=》/app-service001/app1/test
                String path = req.getURI().getRawPath();
                // 新路径=》/app1/test
                String newPath = path.replaceAll(config.regexp, replacement);
                ServerHttpRequest request = req.mutate().path(newPath).build();
                // 将重写后的的URL(http://localhost/app1/test),写到ServerWebExchange的gatewayRequestUrl属性中。
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, request.getURI());
                return chain.filter(exchange.mutate().request(request).build());

2.3 进入全局过滤器 ReactiveLoadBalancerClientFilter

ReactiveLoadBalancerClientFilter通过LoadBalancer 负载均衡器去获取当前服务可用的实际IP地址等信息,然后调用。

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   
     
    	//  lb://app-service001/app1/test
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        // lb 
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
   
     
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
   
     
                log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
            }
			// lb://app-service001/app1/test
            URI requestUri = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
            // 服务名=》app-service001
            String serviceId = requestUri.getHost();
            Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(this.clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class);
            // 创建客户端请求对象
            DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest(new RequestDataContext(new RequestData(exchange.getRequest()), this.getHint(serviceId, this.loadBalancerProperties.getHint())));
            return this.choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext((response) -> {
   
     
                if (!response.hasServer()) {
   
     
                    supportedLifecycleProcessors.forEach((lifecycle) -> {
   
     
                        lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, response));
                    });
                    throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
                } else {
   
     
                	// 获取到可用服务的实际IP 地址和端口
                    ServiceInstance retrievedInstance = (ServiceInstance)response.getServer();
                    // 访问路径 http://localhost/app1/test
                    URI uri = exchange.getRequest().getURI();
                    // 请求协议 HTTP
                    String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
                    if (schemePrefix != null) {
   
     
                        overrideScheme = url.getScheme();
                    }
                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, overrideScheme);
                    // 实际访问地址=》http://192.168.58.1:9000/app1/test
                    URI requestUrl = this.reconstructURI(serviceInstance, uri);
                    if (log.isTraceEnabled()) {
   
     
                        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                    }
					// 将实际的访问地址塞到请求对象中,供后续过滤器去调用访问。
                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);
                    supportedLifecycleProcessors.forEach((lifecycle) -> {
   
     
                        lifecycle.onStartRequest(lbRequest, response);
                    });
                }
            }).then(chain.filter(exchange)).doOnError((throwable) -> {
   
     
                supportedLifecycleProcessors.forEach((lifecycle) -> {
   
     
                    lifecycle.onComplete(new CompletionContext(Status.FAILED, throwable, lbRequest, (Response)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR)));
                });
            }).doOnSuccess((aVoid) -> {
   
     
                supportedLifecycleProcessors.forEach((lifecycle) -> {
   
     
                    lifecycle.onComplete(new CompletionContext(Status.SUCCESS, lbRequest, (Response)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR), new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest()))));
                });
            });
        } else {
   
     
            return chain.filter(exchange);
        }
    }

最终,ReactiveLoadBalancerClientFilter完成了动态服务名到实际访问地址的转换,再经过其他过滤器去调动,获取到响应,最终返回到网关,再响应给页面,整个流程就结束了。

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: