• spring-cloud-gateway服务网关学习




    一、概念

    服务网关是整个微服务架构中对外开放的统一入口,所有的客户端都通过统一的网关使用微服务,服务网关起到了对外隔离内部系统的作用,是微服务的一个标配组件。

    服务网关有以下特点:

    • 高并发:服务网关必须具备高并发处理能力。
    • 安全:通常会在服务网关做权限认证,黑白名单等保证网关安全的功能。
    • 路由转发:所有的外部的请求都需要先经过网关服务,由网关服务统一转发到对应的服务。
    • 监控和限流:服务网关要具备监控所有的流量情况,并在请求压力较大的情况下,进行必要的限流操作,保障整个系统的稳定运行。
    • 灰度发布:当某一个微服版本更新上线的时候,可以利用服务网关进行流量切换,实现改为服务的灰度发布。
    • 服务重试:当服务网关调用某一个微服务失败之后,要使用一定重试策略来尝试调用该微服务。
    • 服务别名:可以在服务网关中给某一个或者某一些微服务设置别名,从而对外屏蔽该微服务的真实信息。

    spring cloud gateway是网关服务的一个具体实现,基于异步非阻塞模型开发,性能上有很大的提升,提供了以下几个特点:

    • 基于spring framework,spring boot构建,更适应目前的一个微服务发展趋势。
    • 动态路由:能够匹配任何请求属性,通过断言(predicate)和过滤器(filter)能更加灵活的处理各种场景问题。
    • 本身实现了限流和负载均衡功能,还支持路由重写。

    二、gateway三大组件

    • router(路由):请求最终实际上应该被转发的样子。
    • predicate(断言):当请求符合某一种规则的时候,就触发路由,也就是匹配条件。
    • filter(过滤器):使用过滤器,请求被路由之前或者之后进行修改。比如在路由之前进行权限校验,登录认证等,在路由之前修改响应结果,流量监控等。

    1、内置断言

    • 基于时间。比如在某个时间之前,某个时间之后,某两个时间之间

      • - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
        
        • 1
        • 2
        • 3
    • 基于Cookie中的某一个key的值,支持正则表达式

      • - Cookie=chocolate, ch.p # 值支持正则表达式
        
        • 1
    • 基于请求头中的某一个key的值,支持正则表达式

      • - Header=X-Request-Id, \d+
        
        • 1
    • 基于域名

      • - Host=**.somehost.org,**.anotherhost.org
        
        • 1
    • 基于请求方式,GET请求或者POST请求或者其他

      • - Method=GET,POST
        
        • 1
    • 基于请求路劲,路径中包含某一个单词

      • - Path=/red/{segment},/blue/{segment}
        
        • 1
    • 基于IP地址

      • - RemoteAddr=192.168.1.1/24
        
        • 1

    2、自定义断言工厂

    • 必须被Spring管理

    • 类名必须使用RoutePredicateFactory结尾

    • 必须定义一个静态内部类,并定义一些属性来接受参数。

      示例:

      基于自定义断言工厂实现:

      • - CheckName=test
        
        • 1
      @Component
      class CheckNameRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckNameRoutePredicateFactory.Config> {
      
         public CheckNameRoutePredicateFactory() {
            super(CheckNameRoutePredicateFactory.Config.class);
         }
      
         @Override
         public List<String> shortcutFieldOrder() {
            return Arrays.asList("name"); // config中定义的属性,需要从这里进行绑定
         }
      
         @Override
         public Predicate<ServerWebExchange> apply(final Config config) {
            return new GatewayPredicate() {
               @Override
               public boolean test(ServerWebExchange exchange) {
      
                  if (config.getName().equals("test")) {
                     return true;
                  }
      
                  return false;
      
               }
            };
         }
      
         @Validated
         public static class Config {
            private String name;
      
            public String getName() {
               return name;
            }
      
            public void setName(String name) {
               this.name = name;
            }
         }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41

    3、过滤器

    主要是请求的时候处理请求,响应的时候处理响应。具体参照官方文档。

    4、自定义过滤器

    • 必须被Spring管理

    • 类名必须使用GatewayFilterFactory结尾

    • 必须定义一个静态内部类,并定义一些属性来接受参数。

      示例:

      基于自定义过滤器工厂实现:

      • - CheckName=age,8
        
        • 1
    @Component
    class CheckAgeGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAgeGatewayFilterFactory.Config> {
    
       public CheckAgeGatewayFilterFactory() {
          super(CheckAgeGatewayFilterFactory.Config.class);
       }
    
       @Override
       public List<String> shortcutFieldOrder() {
          return Arrays.asList("age");
       }
    
       @Override
       public GatewayFilter apply(CheckAgeGatewayFilterFactory.Config config) {
          return this.apply(config.age);
       }
    
       public GatewayFilter apply(int age) {
          return new GatewayFilter() {
             @Override
             public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                int age1 = Integer.parseInt(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("age")));
    				int age1 = Integer.parseInt(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("age")));
    				if (age1 > 18 && age == age1) {
    					return chain.filter(exchange);
    				} else {
    					exchange.getResponse().setStatusCode(HttpStatus.valueOf(404));
    					return exchange.getResponse().setComplete();
    				}
             }
          };
       }
    
       public static class Config {
          int age;
    
          public int getAge() {
             return age;
          }
    
          public void setAge(int age) {
             this.age = age;
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    5、自定义全局过滤器

    @Component
    class LogFilter implements GlobalFilter {
    
       @Override
       public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
          System.out.println("全局过滤器");
          return chain.filter(exchange);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6、跨域问题

    • 配置文件方式
    spring:
      cloud:
        gateway:
          globalcors:
            cors-configurations:
              '[/**]':
                allowedOrigins: "https://docs.spring.io"
                allowedMethods:
                - GET
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • JAVA代码方式
    @Component
    @Configuration
    class CorsWebConfig {
    
       @Bean
       public CorsWebFilter corsWebFilter() {
          CorsConfiguration corsConfiguration = new CorsConfiguration();
          corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
          corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
          corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
    
          UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
          corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
    
          return new CorsWebFilter(corsConfigurationSource);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    7、简单的一个gateway路由转发和跨域处理的配置

    spring:
      application:
        name: cloud-reading-gateway # 网关应用名称
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启网关的自动发现功能
              lowerCaseServiceId: true 
          routes:
            - id: cloud-reading-home-rpc # 路由的ID,名字不重复即可。
              uri: lb://cloud-reading-home # 需要转发的目标服务器地址,lb://cloud-reading-home的写法需要和注册中心结合使用,代表在注册中心上找cloud-reading-home服务,lb://代表使用gateway默认的负载均衡策略向cloud-reading-home服务转发请求。
              predicates:
                - Path=/cloud-reading-home/** # 请求路劲中有cloud-reading-home的时候,就将请求转发到cloud-reading-home服务上
              filters:
                - StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-home/index,经过这里处理后就会变成http://localhost:port/index
    
            - id: cloud-reading-book-rpc
              uri: lb://cloud-reading-book
              predicates:
                - Path=/cloud-reading-book/** # 请求路劲中有cloud-reading-book的时候,就将请求转发到cloud-reading-book服务上
              filters:
                - StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-book/index,经过这里处理后就会变成http://localhost:port/index
    
            - id: cloud-reading-accoud-rpc
              uri: lb://cloud-reading-accoud
              predicates:
                - Path=/cloud-reading-accoud/** # 请求路劲中有cloud-reading-accoud的时候,就将请求转发到cloud-reading-accoud服务上
              filters:
                - StripPrefix=1 # 将请求路劲中的test-serv剪裁掉,假如:http://localhost:8090/cloud-reading-accoud/index,经过这里处理后就会变成http://localhost:port/index
    
    
          # 跨域处理
          globalcors:
            cors-configurations:
              '[/**]': # 允许跨域访问的资源
                allowedOrigins: "*" # 允许的来源
                allowedMethods:
                  - GET
                  - POST
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    三、启动时源码解析

    • GatewayAutoConfiguration.java:网关的核心配置

      // 查找匹配到路由Route并进行处理
      @Bean
      public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
          return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
      }
      // 加载网关配置,初始化的时候,将配置文件中的信息保存在GatewayProperties中。
      @Bean
      public GatewayProperties gatewayProperties() {
          return new GatewayProperties();
      }
      // 创建一个根据RouteDefinition抓换的路由定位器
      @Bean
      public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
              return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, configurationService);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • GatewayLoadBalancerClientAutoConfiguration.java:负载均衡相关配置信息

      // 初始化路由负载过滤器,实际上就是一个全局过滤器
      @Bean
      @ConditionalOnBean({LoadBalancerClient.class})
      @ConditionalOnMissingBean({LoadBalancerClientFilter.class, ReactiveLoadBalancerClientFilter.class})
      @ConditionalOnEnabledGlobalFilter
      public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
          return new LoadBalancerClientFilter(client, properties);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    四、Http请求spring cloud gateway的流程

    1、DispatcherHandler.handle(ServerWebExchange exchange)

    所有的请求都会经过DispatcherHandler.handle(ServerWebExchange exchange)这个方法,这个类就可以理解为MVC中的ServletDispatcher。

    • DispatcherHandler.handle(ServerWebExchange exchange)

      @Override
      public Mono<Void> handle(ServerWebExchange exchange) {
      	return Flux.fromIterable(this.handlerMappings)
                  // 处理当前请求,实际上就是需要找当前请求所对应的路由信息。并保存在当前请求上下文exchange中。
      			.concatMap(mapping -> mapping.getHandler(exchange))
      			.next()
      			.switchIfEmpty(createNotFoundError())
              	// 找到当前请求对应的路由信息之后,就是在这里调用执行的。
      			.flatMap(handler -> invokeHandler(exchange, handler))
      			.flatMap(result -> handleResult(exchange, result));
      }
      
      // mapping.getHandler(exchange))会调用AbstractHandlerMapping.getHandler
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • AbstractHandlerMapping.getHandler

         @Override
         public Mono<Object> getHandler(ServerWebExchange exchange) {
            return getHandlerInternal(exchange).map(handler -> {
                ……省略部分代码……
               return handler;
            });
         }
         // getHandlerInternal(exchange)最终会调用RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)

         @Override
         protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
         ……省略部分代码……
            // 可以理解为匹配当前请求所满足的路由信息。
            return lookupRoute(exchange).flatMap((Function<Route, Mono<?>>) r -> {
         ……省略部分代码……
         			// 将目前找到的router封装到当前的请求上下文中。那么后续肯定会在调用getHandlerInternal的地方使用处理。
                     exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
         	}
         ……省略部分代码……
         }
         // lookupRoute(exchange)最终会调用RouteDefinitionRouteLocator.getRoutes()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • RouteDefinitionRouteLocator.getRoutes()

        @Override
        public Flux<Route> getRoutes() {
            // yml文件中的配置的routers信息会被封装到RouteDefinition中,getRouteDefinitions就是获取定义的所有的routers信息
           Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
               // 把RouteDefinition转化为真正的能够使用的Router
                 .map(this::convertToRoute);
            ……省略部分代码……
           return routes.map(route -> {
              if (logger.isDebugEnabled()) {
                 logger.debug("RouteDefinition matched: " + route.getId());
              }
              return route;
           });
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
    • FilteringWebHandler.handle:DispatcherHandler.handle(ServerWebExchange exchange)方法中的invokeHandler(exchange, handler)最终会执行到此处。

      @Override
      public Mono<Void> handle(ServerWebExchange exchange) {
          // 获取RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)方法中设置的GATEWAY_ROUTE_ATTR属性,实际上就是当前请求所对应路由信息
         Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
          // 从当前请求所对应路由信息中获取到所有的过滤器
         List<GatewayFilter> gatewayFilters = route.getFilters();
         // 根据globalFilters属性,可以判断这里就是的将所有的全局过滤器进行组装成了一个List集合。
         List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
          // 将当前请求所对应路由信息中获取到所有的过滤器添加到全局过滤器中的集合中。
         combined.addAll(gatewayFilters);
      	// 对所有的过滤器按照设定的Order进行排序
         AnnotationAwareOrderComparator.sort(combined);
      
      	// 创建一个过滤器执行链(责任链模式),并调用filter依次执行所有的过滤器方法。
         return new DefaultGatewayFilterChain(combined).filter(exchange);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

    五、LoadBalancerClientFilter负载均衡策略过滤器

    gateway默认整合Ribbon做负载均衡策略

    • LoadBalancerClientFilter.filter–> choose(exchange)–>RibbonLoadBalancerClient.choose(String serviceId)–>this.getServer(this.getLoadBalancer(serviceId), hint)–>getServer(ILoadBalancer loadBalancer, Object hint)–>loadBalancer.chooseServer(hint != null ? hint : “default”)–>ZoneAwareLoadBalancer.chooseServer(Object key)–>zoneLoadBalancer.chooseServer(key)–>BaseLoadBalancer.chooseServer(Object key)–>this.rule.choose(key)–>PredicateBasedRule.choose(Object key)–>this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key)–>AbstractServerPredicate.chooseRoundRobinAfterFiltering(List servers, Object loadBalancerKey)–>this.incrementAndGetModulo(eligible.size())
    // Ribbon默认采用轮询方式做负载均衡,ribbon默认的轮训方式实现
    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextIndex.get();
            next = (current + 1) % modulo;
        } while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);
    
        return current;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    六、NettyRoutingFilter是Filter执行链中最后执行的Filter,也是最终调用路由的地方

    上述所有的步骤都是在做一个事情,那就是匹配当前请求中所适配的路由,并将路由的信息拼装当前请求上下文中,比如:我们请求订单服务,但是得先经过gateway服务,那我们请求网关服务的URL可能是http://localhost:8081/order/createOrder?id=1,上述的所有步骤执行完成之后,我们的URL才能变成实际上订单服务的地址http://192.168.12.11:8000/createOrder?id=1(假设我们的订单服务部署在192.168.12.11机器上,开放端口为8000),这个URL地址才是真正能创建订单的地址。但是上述步骤都只是在组装这个订单服务的地址http://192.168.12.11:8000/createOrder?id=1,在哪里调用呢,实际上就是在NettyRoutingFilter中调用的,NettyRoutingFilter是Filter执行链中最后执行的Filter,也是最终调用路由的地方,此处会真正的执行http://192.168.12.11:8000/createOrder?id=1这个请求,调到订单服务上,创建一个真正的订单。

    • NettyRoutingFilter.filter(ServerWebExchange exchange, GatewayFilterChain chain):过滤器的执行方法,在上述过滤器链中会调用执行。
    @Override
    @SuppressWarnings("Duplicates")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 这里获取到上述步骤中组装好的真正的要请求订单服务的地址:http://192.168.12.11:8000/createOrder?id=1
       URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
       ……省略部分代码……
        // 
       Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
             .headers(headers -> {
    	……省略部分代码……
                return Mono.just(res);
             });
    
    	……省略部分代码……
       return responseFlux.then(chain.filter(exchange));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • getHttpClient(route, exchange):就是在这里调用http://192.168.12.11:8000/createOrder?id=1这个方法的。
    protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
       Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR);
       if (connectTimeoutAttr != null) {
          Integer connectTimeout = getInteger(connectTimeoutAttr);
           // 最终也就是通过httpClient调用
          return this.httpClient.tcpConfiguration((tcpClient) -> tcpClient
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout));
       }
       return httpClient;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    c++单元测试
    计算机视觉|投影与三维视觉
    [附源码]java毕业设计医院疫情疾控管理系统
    C++项目实战——基于多设计模式下的同步&异步日志系统-⑪-日志器管理类与全局建造者类设计(单例模式)
    JavaWeb(一)
    抢占式调度是如何发生的
    浅析微前端框架 single-spa
    sql2java-excel(二):基于apache poi实现数据库表的导出的spring web支持
    .NET借助虚拟网卡实现一个简单异地组网工具
    嵌入式学习第二十五天!(网络的概念、UDP编程)
  • 原文地址:https://blog.csdn.net/qq_22610595/article/details/126023806