文章目錄

  • 一、概念
  • 二、gateway三大組件
  • 1、內置斷言
  • 2、自定義斷言工廠
  • 3、過濾器
  • 4、自定義過濾器
  • 5、自定義全局過濾器
  • 6、跨域問題
  • 7、簡單的一個gateway路由轉發和跨域處理的配置
  • 三、啓動時源碼解析
  • 四、Http請求spring cloud gateway的流程
  • 1、DispatcherHandler.handle(ServerWebExchange exchange)
  • 五、LoadBalancerClientFilter負載均衡策略過濾器
  • 六、NettyRoutingFilter是Filter執行鏈中最後執行的Filter,也是最終調用路由的地方

一、概念

服務網關是整個微服務架構中對外開放的統一入口,所有的客户端都通過統一的網關使用微服務,服務網關起到了對外隔離內部系統的作用,是微服務的一個標配組件。

服務網關有以下特點:

  • 高併發:服務網關必須具備高併發處理能力。
  • 安全:通常會在服務網關做權限認證,黑白名單等保證網關安全的功能。
  • 路由轉發:所有的外部的請求都需要先經過網關服務,由網關服務統一轉發到對應的服務。
  • 監控和限流:服務網關要具備監控所有的流量情況,並在請求壓力較大的情況下,進行必要的限流操作,保障整個系統的穩定運行。
  • 灰度發佈:當某一個微服版本更新上線的時候,可以利用服務網關進行流量切換,實現改為服務的灰度發佈。
  • 服務重試:當服務網關調用某一個微服務失敗之後,要使用一定重試策略來嘗試調用該微服務。
  • 服務別名:可以在服務網關中給某一個或者某一些微服務設置別名,從而對外屏蔽該微服務的真實信息。

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]
  • 基於Cookie中的某一個key的值,支持正則表達式
- Cookie=chocolate, ch.p # 值支持正則表達式
  • 基於請求頭中的某一個key的值,支持正則表達式
- Header=X-Request-Id, \d+
  • 基於域名
- Host=**.somehost.org,**.anotherhost.org
  • 基於請求方式,GET請求或者POST請求或者其他
- Method=GET,POST
  • 基於請求路勁,路徑中包含某一個單詞
- Path=/red/{segment},/blue/{segment}
  • 基於IP地址
- RemoteAddr=192.168.1.1/24

2、自定義斷言工廠

  • 必須被Spring管理
  • 類名必須使用RoutePredicateFactory結尾
  • 必須定義一個靜態內部類,並定義一些屬性來接受參數。
    示例:
    基於自定義斷言工廠實現:
- CheckName=test
@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;
      }
   }
}

3、過濾器

主要是請求的時候處理請求,響應的時候處理響應。具體參照官方文檔。

4、自定義過濾器

  • 必須被Spring管理
  • 類名必須使用GatewayFilterFactory結尾
  • 必須定義一個靜態內部類,並定義一些屬性來接受參數。
    示例:
    基於自定義過濾器工廠實現:
- CheckName=age,8
@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;
      }
   }
}

5、自定義全局過濾器

@Component
class LogFilter implements GlobalFilter {

   @Override
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      System.out.println("全局過濾器");
      return chain.filter(exchange);
   }
}

6、跨域問題

  • 配置文件方式
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "https://docs.spring.io"
            allowedMethods:
            - GET
  • 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);
   }
}

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

三、啓動時源碼解析

  • 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);
}
  • GatewayLoadBalancerClientAutoConfiguration.java:負載均衡相關配置信息
// 初始化路由負載過濾器,實際上就是一個全局過濾器
@Bean
@ConditionalOnBean({LoadBalancerClient.class})
@ConditionalOnMissingBean({LoadBalancerClientFilter.class, ReactiveLoadBalancerClientFilter.class})
@ConditionalOnEnabledGlobalFilter
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
    return new LoadBalancerClientFilter(client, properties);
}

四、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
  • AbstractHandlerMapping.getHandler
@Override
   public Mono<Object> getHandler(ServerWebExchange exchange) {
      return getHandlerInternal(exchange).map(handler -> {
          ……省略部分代碼……
         return handler;
      });
   }
   // getHandlerInternal(exchange)最終會調用RoutePredicateHandlerMapping.getHandlerInternal(ServerWebExchange exchange)
  • 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()
  • 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;
   });
}
  • 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);
}

五、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;
}

六、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));
}
  • 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;
}