Spring Cloud Gateway 源碼分析及應(yīng)用

一、簡(jiǎn)介

Spring Cloud Gateway 是Spring Cloud 生態(tài)全新項(xiàng)目,基于Spring 5、Spring Boot 2.X姻采、Project Reactor實(shí)現(xiàn)的API網(wǎng)關(guān),旨在為微服務(wù)提供簡(jiǎn)單高效的API路由管理方法爵憎。
Spring Cloud Gateway 作為Spring Cloud 生態(tài)中的網(wǎng)關(guān)慨亲,目標(biāo)是代替Zuul 1.X。Spring Cloud 2.X版本目前仍未對(duì)Zuul 2.X高性能版本進(jìn)行集成宝鼓,仍使用的是非Reactor的老版本Zuul網(wǎng)關(guān)刑棵。

  • 目前Spring Cloud dependencies 最新版本Hoxton.SR8 仍使用的是Zuul 1.3.1
  • Zuul 2.x 高性能Reactor版本本身與18年5月開(kāi)源,目前最新版本2.1.9

為了提高網(wǎng)關(guān)性能愚铡,Spring Cloud Gateway基于WebFlux框架實(shí)現(xiàn)蛉签,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty。

1.1 術(shù)語(yǔ)

  • Route: Gateway的基本構(gòu)建模塊沥寥,由ID碍舍、目標(biāo)URL、謂詞集合和過(guò)濾器集合定義邑雅。
  • Predicate: Java8 Funciton Predicate片橡,輸入類(lèi)型是 SpringFramework ServerWebExchange,可以匹配HTTP請(qǐng)求的所有內(nèi)容淮野,比如標(biāo)頭或參數(shù)锻全。
  • Filter:使用特定工廠構(gòu)造的Spring FrameworkGatewayFilter實(shí)例狂塘,可以在發(fā)送下游請(qǐng)求之前或之后修改請(qǐng)求或響應(yīng)录煤。

1.3 特性

  • 動(dòng)態(tài)路由:能夠匹配任何請(qǐng)求屬性鳄厌;
  • 可以對(duì)路由指定 Predicate(斷言)和 Filter(過(guò)濾器);
  • 集成Hystrix的斷路器功能妈踊;
  • 集成 Spring Cloud 服務(wù)發(fā)現(xiàn)功能了嚎;
  • 易于編寫(xiě)的 Predicate(斷言)和 Filter(過(guò)濾器);
  • 請(qǐng)求限流功能廊营;
  • 支持路徑重寫(xiě)

1.4 Spring Cloud Gateway與Spring Cloud Zuul

Spring Cloud Zuul

Springcloud 2.x 版本到目前為止中所集成的Zuul版本(1.x)歪泳,采用的是Tomcat容器,使用的是傳統(tǒng)的Servlet IO處理模型露筒。
servlet由servlet container進(jìn)行生命周期管理呐伞。container啟動(dòng)時(shí)構(gòu)造servlet對(duì)象并調(diào)用servlet init()進(jìn)行初始化;container關(guān)閉時(shí)調(diào)用servlet destory()銷(xiāo)毀servlet慎式;container運(yùn)行時(shí)接受請(qǐng)求伶氢,并為每個(gè)請(qǐng)求分配一個(gè)線程(一般從線程池中獲取空閑線程)然后調(diào)用service()。
弊端:servlet是一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)IO模型瘪吏,當(dāng)請(qǐng)求進(jìn)入servlet container時(shí)癣防,servlet container就會(huì)為其綁定一個(gè)線程,在并發(fā)不高的場(chǎng)景下這種模型是適用的掌眠,但是一旦并發(fā)上升蕾盯,線程數(shù)量就會(huì)上漲,而線程資源代價(jià)是昂貴的(上線文切換蓝丙,內(nèi)存消耗大)嚴(yán)重影響請(qǐng)求的處理時(shí)間级遭。在一些簡(jiǎn)單的業(yè)務(wù)場(chǎng)景下,不希望為每個(gè)request分配一個(gè)線程渺尘,只需要1個(gè)或幾個(gè)線程就能應(yīng)對(duì)極大并發(fā)的請(qǐng)求挫鸽,這種業(yè)務(wù)場(chǎng)景下servlet模型沒(méi)有優(yōu)勢(shì)。

Zuul請(qǐng)求處理模型

所以Springcloud Zuul 是基于servlet之上的一個(gè)阻塞式處理模型沧烈,即spring實(shí)現(xiàn)了處理所有request請(qǐng)求的一個(gè)servlet(DispatcherServlet)掠兄,并由該servlet阻塞式處理處理。所以Springcloud Zuul無(wú)法擺脫servlet模型的弊端锌雀。

Webflux模型

Webflux模式替換了舊的Servlet線程模型蚂夕。用少量的線程處理request和response io操作,這些線程稱(chēng)為L(zhǎng)oop線程腋逆,而業(yè)務(wù)交給響應(yīng)式編程框架處理婿牍,響應(yīng)式編程是非常靈活的,用戶(hù)可以將業(yè)務(wù)中阻塞的操作提交到響應(yīng)式框架的work線程中執(zhí)行惩歉,而不阻塞的操作依然可以在Loop線程中進(jìn)行處理等脂,大大提高了Loop線程的利用率俏蛮。官方結(jié)構(gòu)圖:

Webflux雖然可以兼容多個(gè)底層的通信框架,但是一般情況下上遥,底層使用的還是Netty搏屑,畢竟,Netty是目前業(yè)界認(rèn)可的最高性能的通信框架粉楚。而Webflux的Loop線程辣恋,正好就是著名的Reactor 模式IO處理模型的Reactor線程,如果使用的是高性能的通信框架Netty模软,這就是Netty的EventLoop線程伟骨。

1.5 如何集成Gateway

使用Gateway只需要簡(jiǎn)單引入依賴(lài):

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

二、工作原理



處理流程:

  1. Gateway接受客戶(hù)端請(qǐng)求燃异;
  2. 網(wǎng)關(guān)處理程序映射確定請(qǐng)求與路由匹配携狭,匹配成功則將其發(fā)送到網(wǎng)關(guān)Web處理程序;
  3. Web處理程序處理程序通過(guò)特定于請(qǐng)求的過(guò)濾器鏈運(yùn)行請(qǐng)求:
    • 請(qǐng)求經(jīng)過(guò) Filter 過(guò)濾器鏈回俐,執(zhí)行 pre 處理邏輯逛腿,如修改請(qǐng)求頭信息等。
    • 發(fā)出代理請(qǐng)求鲫剿,請(qǐng)求被轉(zhuǎn)發(fā)至下游服務(wù)并返回響應(yīng)鳄逾。
  4. 響應(yīng)經(jīng)過(guò) Filter 過(guò)濾器鏈,執(zhí)行 post 處理邏輯灵莲。
  5. 向客戶(hù)端響應(yīng)應(yīng)答雕凹。

注意,在沒(méi)有端口的路由中定義的URI政冻,HTTP和HTTPS URI的默認(rèn)端口值分別為80和443枚抵。

  • DispatcherHandler:所有請(qǐng)求的調(diào)度器,負(fù)載請(qǐng)求分發(fā)
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
  @Nullable
  private List<HandlerMapping> handlerMappings;
  @Nullable
  private List<HandlerAdapter> handlerAdapters;
  @Nullable
  private List<HandlerResultHandler> resultHandlers;

  public DispatcherHandler() {
  }

  public DispatcherHandler(ApplicationContext applicationContext) {
    this.initStrategies(applicationContext);
  }

  @Nullable
  public final List<HandlerMapping> getHandlerMappings() {
    return this.handlerMappings;
  }

  public void setApplicationContext(ApplicationContext applicationContext) {
    this.initStrategies(applicationContext);
  }

  # 初始明场、校驗(yàn)HandlerMapping并按order排序
  protected void initStrategies(ApplicationContext context) {
    Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    ArrayList<HandlerMapping> mappings = new ArrayList(mappingBeans.values());
    AnnotationAwareOrderComparator.sort(mappings);
    this.handlerMappings = Collections.unmodifiableList(mappings);
    Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
    this.handlerAdapters = new ArrayList(adapterBeans.values());
    AnnotationAwareOrderComparator.sort(this.handlerAdapters);
    Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);
    this.resultHandlers = new ArrayList(beans.values());
    AnnotationAwareOrderComparator.sort(this.resultHandlers);
  }
//遍歷handlerMappings 汽摹,根據(jù)exchange找到對(duì)應(yīng)的handler
// 對(duì)于Gateway 會(huì)找到對(duì)應(yīng)的RoutePredicateHandlerMapping
  public Mono<Void> handle(ServerWebExchange exchange) {
    return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
      return mapping.getHandler(exchange);
    }).next().switchIfEmpty(this.createNotFoundError())////如果遍歷不到結(jié)果,則切換到錯(cuò)誤處理
.flatMap((handler) -> {
      //通過(guò)HandlerAdapter調(diào)用handler苦锨,
      //gateway使用的 SimpleHandlerAdapter
      return this.invokeHandler(exchange, handler);
    }).flatMap((result) -> {//對(duì)響應(yīng)進(jìn)行處理
      return this.handleResult(exchange, result);
    });
  }

  private <R> Mono<R> createNotFoundError() {
    return Mono.defer(() -> {
      Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler");
      return Mono.error(ex);
    });
  }

  private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
    if (this.handlerAdapters != null) {
      Iterator var3 = this.handlerAdapters.iterator();

      while(var3.hasNext()) {
        HandlerAdapter handlerAdapter = (HandlerAdapter)var3.next();
        if (handlerAdapter.supports(handler)) {
        //調(diào)用handler的handle方法處理請(qǐng)求
          return handlerAdapter.handle(exchange, handler);
        }
      }
    }

    return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
  }

//根據(jù)result獲取對(duì)應(yīng)的結(jié)果處理handler并處理結(jié)果
  private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    return this.getResultHandler(result).handleResult(exchange, result).checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]").onErrorResume((ex) -> {
      return result.applyExceptionHandler(ex).flatMap((exResult) -> {
        String text = "Exception handler " + exResult.getHandler() + ", error=\"" + ex.getMessage() + "\" [DispatcherHandler]";
        return this.getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text);
      });
    });
  }

  private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
    if (this.resultHandlers != null) {
      Iterator var2 = this.resultHandlers.iterator();

      while(var2.hasNext()) {
        HandlerResultHandler resultHandler = (HandlerResultHandler)var2.next();
        if (resultHandler.supports(handlerResult)) {
          return resultHandler;
        }
      }
    }

    throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
  }
}
  • RoutePredicateHandlerMapping:路由謂語(yǔ)匹配器逼泣,用于路由的查找,以及找到路由后返回對(duì)應(yīng)的WebHandler舟舒,DispatcherHandler會(huì)依次遍歷HandlerMapping集合進(jìn)行處理
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
  private final FilteringWebHandler webHandler;
  private final RouteLocator routeLocator;
  private final Integer managementPort;
  private final RoutePredicateHandlerMapping.ManagementPortType managementPortType;

  public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
    this.webHandler = webHandler;
    this.routeLocator = routeLocator;
    this.managementPort = getPortProperty(environment, "management.server.");
    this.managementPortType = this.getManagementPortType(environment);
    //設(shè)置排序字段1拉庶,此處的目的是Spring Cloud Gateway 的 GatewayWebfluxEndpoint 提供 HTTP API ,不需要經(jīng)過(guò)網(wǎng)關(guān)
   //它通過(guò) RequestMappingHandlerMapping 進(jìn)行請(qǐng)求匹配處理秃励。RequestMappingHandlerMapping 的 order = 0 氏仗,需要排在 RoutePredicateHandlerMapping 前面。所有夺鲜,RoutePredicateHandlerMapping 設(shè)置 order = 1 皆尔。
    this.setOrder(1);
    this.setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
  }

  private RoutePredicateHandlerMapping.ManagementPortType getManagementPortType(Environment environment) {
    Integer serverPort = getPortProperty(environment, "server.");
    if (this.managementPort != null && this.managementPort < 0) {
      return RoutePredicateHandlerMapping.ManagementPortType.DISABLED;
    } else {
      return this.managementPort != null && (serverPort != null || !this.managementPort.equals(8080)) && (this.managementPort == 0 || !this.managementPort.equals(serverPort)) ? RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT : RoutePredicateHandlerMapping.ManagementPortType.SAME;
    }
  }

  private static Integer getPortProperty(Environment environment, String prefix) {
    return (Integer)environment.getProperty(prefix + "port", Integer.class);
  }
//設(shè)置mapping到上下文環(huán)境
  protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
    if (this.managementPortType == RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT && this.managementPort != null && exchange.getRequest().getURI().getPort() == this.managementPort) {
      return Mono.empty();
    } else {
      exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());
      // 查找路由
      return this.lookupRoute(exchange).flatMap((r) -> {
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
        }

         //將查找到的路由設(shè)置到上下文環(huán)境
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
        //返回mapping對(duì)應(yīng)的WebHandler即FilteringWebHandler
 return Mono.just(this.webHandler);
      }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
      //當(dāng)前未找到路由時(shí)返回空呐舔,并移除GATEWAY_PREDICATE_ROUTE_ATTR  exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
        }

      })));
    }
  }

  protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
    return super.getCorsConfiguration(handler, exchange);
  }

  private String getExchangeDesc(ServerWebExchange exchange) {
    StringBuilder out = new StringBuilder();
    out.append("Exchange: ");
    out.append(exchange.getRequest().getMethod());
    out.append(" ");
    out.append(exchange.getRequest().getURI());
    return out.toString();
  }
//通過(guò)路由定位器獲取路由信息
  protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
    return this.routeLocator.getRoutes().concatMap((route) -> {
      return Mono.just(route).filterWhen((r) -> {
        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
        return (Publisher)r.getPredicate().apply(exchange);//通過(guò)謂詞過(guò)濾路由
      }).doOnError((e) -> {
        this.logger.error("Error applying predicate for route: " + route.getId(), e);
      }).onErrorResume((e) -> {
        return Mono.empty();
      });
    }).next().map((route) -> {
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Route matched: " + route.getId());
      }

      this.validateRoute(route, exchange);
      return route;
    });
  }

  protected void validateRoute(Route route, ServerWebExchange exchange) {
  }

  protected String getSimpleName() {
    return "RoutePredicateHandlerMapping";
  }

  public static enum ManagementPortType {
    DISABLED,
    SAME,
    DIFFERENT;

    private ManagementPortType() {
    }
  }
}
  • FilteringWebHandler : 使用Filter鏈表處理請(qǐng)求的WebHandler,RoutePredicateHandlerMapping找到路由后返回對(duì)應(yīng)的FilteringWebHandler對(duì)請(qǐng)求進(jìn)行處理慷蠕,F(xiàn)ilteringWebHandler負(fù)責(zé)組裝Filter鏈表并調(diào)用鏈表處理請(qǐng)求珊拼。
# 通過(guò)過(guò)濾器處理web請(qǐng)求的處理器
public class FilteringWebHandler implements WebHandler {
  protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
# 全局過(guò)濾器
  private final List<GatewayFilter> globalFilters;

  public FilteringWebHandler(List<GlobalFilter> globalFilters) {
    this.globalFilters = loadFilters(globalFilters);
  }

  private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
    return (List)filters.stream().map((filter) -> {
      FilteringWebHandler.GatewayFilterAdapter gatewayFilter = new FilteringWebHandler.GatewayFilterAdapter(filter);
      if (filter instanceof Ordered) {
        int order = ((Ordered)filter).getOrder();
        return new OrderedGatewayFilter(gatewayFilter, order);
      } else {
        return gatewayFilter;
      }
    }).collect(Collectors.toList());
  }

  public Mono<Void> handle(ServerWebExchange exchange) {
#獲取請(qǐng)求上下文設(shè)置的路由實(shí)例
    Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
# 獲取網(wǎng)關(guān)路由定義下的網(wǎng)關(guān)過(guò)濾器集合
    List<GatewayFilter> gatewayFilters = route.getFilters();
# 組合全局的過(guò)濾器與路由配置的過(guò)濾器,并將路由器定義的過(guò)濾器添加集合尾部
    List<GatewayFilter> combined = new ArrayList(this.globalFilters);
    combined.addAll(gatewayFilters);
    AnnotationAwareOrderComparator.sort(combined);
    if (logger.isDebugEnabled()) {
      logger.debug("Sorted gatewayFilterFactories: " + combined);
    }
# 創(chuàng)建過(guò)濾器鏈表對(duì)其進(jìn)行鏈?zhǔn)秸{(diào)用
    return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
  }

  private static class GatewayFilterAdapter implements GatewayFilter {
    private final GlobalFilter delegate;

    GatewayFilterAdapter(GlobalFilter delegate) {
      this.delegate = delegate;
    }

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      return this.delegate.filter(exchange, chain);
    }

    public String toString() {
      StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
      sb.append("delegate=").append(this.delegate);
      sb.append('}');
      return sb.toString();
    }
  }

  private static class DefaultGatewayFilterChain implements GatewayFilterChain {
    private final int index;
    private final List<GatewayFilter> filters;

    DefaultGatewayFilterChain(List<GatewayFilter> filters) {
      this.filters = filters;
      this.index = 0;
    }

    private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {
      this.filters = parent.getFilters();
      this.index = index;
    }

    public List<GatewayFilter> getFilters() {
      return this.filters;
    }

    public Mono<Void> filter(ServerWebExchange exchange) {
      return Mono.defer(() -> {
        if (this.index < this.filters.size()) {
          GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);
          FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1);
          return filter.filter(exchange, chain);
        } else {
          return Mono.empty();
        }
      });
    }
  }
}

2.2 Gateway類(lèi)圖

根據(jù)DispatcherHandler入口整理的Gateway類(lèi)圖


Spring Cloud Gateway的配置由一系列RouteDefinitionLocator實(shí)例驅(qū)動(dòng)砌们。以下清單顯示了RouteDefinitionLocator接口的定義:

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

默認(rèn)情況下杆麸,PropertiesRouteDefinitionLocator使用Spring Boot的@ConfigurationProperties機(jī)制來(lái)加載屬性。

三浪感、配置路由謂詞工廠和網(wǎng)關(guān)過(guò)濾工廠

3.1 兩種不同的配置路由方式

Gateway 提供了兩種不同的方式用于配置路由,一種是通過(guò)yml文件來(lái)配置饼问,另一種是通過(guò)Java Bean來(lái)配置影兽。

通過(guò)yml文件來(lái)配置

service-url:
  user-service: http://localhost:8201
spring:
  cloud:
    gateway:
      routes:
        - id: path_route #路由的ID
          uri: ${service-url.user-service}/user/{id} #匹配后路由地址
          predicates: # 斷言,路徑相匹配的進(jìn)行路由
            - Path=/user/{id}

通過(guò)Java Bean來(lái)配置

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route2", r -> r.path("/user/getByUsername")
                        .uri("http://localhost:8201/user/getByUsername"))
                .build();
    }
}

3.2 Route Predicate 的使用

Spring Cloud Gateway將路由匹配作為Spring WebFluxHandlerMapping基礎(chǔ)架構(gòu)的一部分秋泳。Spring Cloud Gateway包括許多內(nèi)置的路由謂詞工廠屹蚊。所有這些謂詞都與HTTP請(qǐng)求的不同屬性匹配妨马。可以將多個(gè)路由謂詞工廠與邏輯and語(yǔ)句結(jié)合使用捐名。
Predicate 來(lái)源于 Java 8,是 Java 8 中引入的一個(gè)函數(shù)闹击,Predicate 接受一個(gè)輸入?yún)?shù)镶蹋,返回一個(gè)布爾值結(jié)果。該接口包含多種默認(rèn)方法來(lái)將 Predicate 組合成其他復(fù)雜的邏輯(比如:與赏半,或贺归,非)《象铮可以用于接口請(qǐng)求參數(shù)校驗(yàn)拂酣、判斷新老數(shù)據(jù)是否有變化需要進(jìn)行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實(shí)現(xiàn)了各種路由匹配規(guī)則仲义,有通過(guò) Header婶熬、請(qǐng)求參數(shù)等不同的條件來(lái)進(jìn)行作為條件匹配到對(duì)應(yīng)的路由。

下圖為 Spring Cloud Gateway內(nèi)置的幾種常見(jiàn)謂詞路由器:


3.2.1 根據(jù)datetime 匹配

After Route Predicate
在指定時(shí)間之后的請(qǐng)求會(huì)匹配該路由埃撵。

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: ${service-url.user-service}
          predicates:
            - After=2019-09-24T16:30:00+08:00[Asia/Shanghai]

Before Route Predicate
在指定時(shí)間之前的請(qǐng)求會(huì)匹配該路由赵颅。

spring:
  cloud:
    gateway:
      routes:
        - id: before_route
          uri: ${service-url.user-service}
          predicates:
            - Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]

Between Route Predicate
在指定時(shí)間區(qū)間內(nèi)的請(qǐng)求會(huì)匹配該路由。

spring:
  cloud:
    gateway:
      routes:
        - id: before_route
          uri: ${service-url.user-service}
          predicates:
            - Between=2019-09-24T16:30:00+08:00[Asia/Shanghai], 2019-09-25T16:30:00+08:00[Asia/Shanghai]

3.2.2 根據(jù)Cookie匹配

帶有指定Cookie的請(qǐng)求會(huì)匹配該路由盯另。

spring:
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: ${service-url.user-service}
          predicates:
            - Cookie=username,macro

3.2.3 Header Route Predicate

帶有指定請(qǐng)求頭的請(qǐng)求會(huì)匹配該路由性含。

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: ${service-url.user-service}
        predicates:
        - Header=X-Request-Id, \d+

3.2.4 Host Route Predicate

帶有指定Host的請(qǐng)求會(huì)匹配該路由。

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Host=**.qt.com

3.2.5 Method Route Predicate

發(fā)送指定方法的請(qǐng)求會(huì)匹配該路由鸳惯。

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: ${service-url.user-service}
        predicates:
        - Method=GET

3.2.5 Path Route Predicate

發(fā)送指定路徑的請(qǐng)求會(huì)匹配該路由商蕴。

spring:
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: ${service-url.user-service}/user/{id}
          predicates:
            - Path=/user/{id}

3.2.6 Query Route Predicate

帶指定查詢(xún)參數(shù)的請(qǐng)求可以匹配該路由叠萍。

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: ${service-url.user-service}/user/getByUsername
        predicates:
        - Query=username

3.2.7 RemoteAddr Route Predicate

從指定遠(yuǎn)程地址發(fā)起的請(qǐng)求可以匹配該路由。

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: ${service-url.user-service}
        predicates:
        - RemoteAddr=192.168.1.1/24

3.3 Route Filter 的使用

根據(jù)Gateway工作原理绪商,我們知道Gateway實(shí)際是由路由匹配到的一系列Filter過(guò)濾鏈來(lái)處理請(qǐng)求的苛谷,Spring Cloud Gateway包括許多內(nèi)置的GatewayFilter工廠。具體詳情參考官網(wǎng):
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

3.4 Global Filters 全局過(guò)濾器

當(dāng)請(qǐng)求與路由匹配時(shí)格郁,過(guò)濾Web處理程序會(huì)將的所有實(shí)例GlobalFilter和所有特定GatewayFilter于路由的實(shí)例添加到過(guò)濾器鏈中腹殿。該組合的過(guò)濾器鏈按org.springframework.core.Ordered接口排序,可以通過(guò)實(shí)現(xiàn)該getOrder()方法進(jìn)行設(shè)置例书。
Spring Cloud Gateway區(qū)分了執(zhí)行過(guò)濾器邏輯的“前”和“后”階段锣尉,因此優(yōu)先級(jí)最高的過(guò)濾器是“前”階段的第一個(gè),而“后”階段的最后一個(gè)是優(yōu)先級(jí)最低的一個(gè)决采。

例如自沧,下面程序配置了一個(gè)過(guò)濾器鏈:

@Bean
public GlobalFilter customFilter() {
    return new CustomGlobalFilter();
}

public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("custom global filter");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

四、 結(jié)合注冊(cè)中心和配置中心使用

Gateway會(huì)根據(jù)注冊(cè)中心注冊(cè)的服務(wù)列表树瞭,以服務(wù)名為路徑創(chuàng)建動(dòng)態(tài)路由拇厢。這里主要使用Nacos作為注冊(cè)中心和配置中心

4.1 使用動(dòng)態(tài)路由

4.1.1 基本配置

引入依賴(lài)

    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

啟用DiscoveryClient網(wǎng)關(guān)集成

# spring.cloud.gateway.discovery.locator.enabled=true
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開(kāi)啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能
          lower-case-service-id: true #使用小寫(xiě)服務(wù)名,默認(rèn)是大寫(xiě)

使用網(wǎng)關(guān)訪問(wèn)服務(wù):

C:\Users\liangbodlz\.ssh>curl 192.168.132.49:1500/nacos-provider/index
Hello!

4.1.2 使用Route Predicate Factory過(guò)濾器實(shí)現(xiàn)通過(guò)指定path訪問(wèn)服務(wù)

在實(shí)際生產(chǎn)環(huán)境中晒喷,我們往往不會(huì)通過(guò)服務(wù)的application-name來(lái)訪問(wèn)服務(wù)孝偎,而是通過(guò)某個(gè)固定的url path來(lái)訪問(wèn),比如xx.xxx/user/login凉敲,來(lái)訪問(wèn)用戶(hù)服務(wù)的接口
通過(guò)Spring Cloud Gateway 內(nèi)置 Path Route Predicate Factory 可以實(shí)現(xiàn)該目標(biāo):

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開(kāi)啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能
          lower-case-service-id: true #使用小寫(xiě)服務(wù)名衣盾,默認(rèn)是大寫(xiě)
      routes:
      - id: nacos-provider
        uri: lb://nacos-provider
        predicates:
          - Path=/nprovider/**
        filters:
          - StripPrefix=1

使用指定path訪問(wèn)服務(wù)

C:\Users\liangbodlz\.ssh>curl 192.168.132.49:1500/nprovider/index
Hello!
C:\Users\liangbodlz\.ssh>

4.1.3 使用Nacos數(shù)據(jù)源動(dòng)態(tài)加載和刷新路由配置

通常我們將微服務(wù)的Route Predicate Path和Gateway應(yīng)用本身的配置放在一起,但是隨著微服務(wù)的擴(kuò)展荡陷,Route Predicate Path會(huì)逐漸增加導(dǎo)致Gateway 服務(wù)配置會(huì)變得臃腫雨效,且Route Predicate Path配置會(huì)隨著服務(wù)的增減進(jìn)行變更,而更新的路由配置生效需要重啟Gateway废赞,這都是實(shí)際線上環(huán)境不可忍受的徽龟。因此獨(dú)立管理Route Predicate Path配置且支持動(dòng)態(tài)刷新配置變得必要起來(lái)。

基于上述需求唉地,我們可以考慮將Gateway 路由配置存儲(chǔ)到內(nèi)存或者其他介質(zhì)中据悔。
從源碼分析中可以知道Gateway路由配置信息由RouteDefinitionLocator 接口完成。

RouteDefinitionLocator實(shí)現(xiàn)關(guān)系

RouteDefinitionLocator 是Gateway路由配置讀取的頂級(jí)接口耘沼,提供從緩存极颓、配置文件、服務(wù)注冊(cè)中心群嗤、組合等不同方式讀取配置菠隆,以及提供RouteDefinitionRepository 接口方式對(duì)RouteDefinition進(jìn)行增、刪、查操作骇径。要自定義路由配置實(shí)現(xiàn)可以考慮從上述接口著手實(shí)現(xiàn)躯肌。
這里主要基于Nacos配置中心+RouteDefinitionRepository 自定義路由配置加載,并參考破衔,CachingRouteLocator實(shí)現(xiàn)路由配置的動(dòng)態(tài)刷新
核心源碼清單

//自定義路由配置加載核心接口
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}

//查詢(xún)路由
public interface RouteDefinitionLocator {
  //返回自定義路由配置加載
  Flux<RouteDefinition> getRouteDefinitions();
}

//路由增清女、刪
public interface RouteDefinitionWriter {
  Mono<Void> save(Mono<RouteDefinition> route);

  Mono<Void> delete(Mono<String> routeId);
}
//動(dòng)態(tài)路由刷新實(shí)現(xiàn)
public class CachingRouteLocator implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {
....//省略
  private ApplicationEventPublisher applicationEventPublisher;
.....//省略
  public void onApplicationEvent(RefreshRoutesEvent event) {
    try {
      this.fetch().collect(Collectors.toList()).subscribe((list) -> {
        Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe((signals) -> {
          this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
          this.cache.put("routes", signals);
        }, (throwable) -> {
          this.handleRefreshError(throwable);
        });
      });
    } catch (Throwable var3) {
      this.handleRefreshError(var3);
    }

  }

  private void handleRefreshError(Throwable throwable) {
    if (log.isErrorEnabled()) {
      log.error("Refresh routes error !!!", throwable);
    }

    this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this, throwable));
  }

代碼實(shí)現(xiàn)

//實(shí)現(xiàn)RouteDefinitionRepository接口
package com.easy.mall.route;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import com.easy.mall.config.GatewayConfig;
import com.easy.mall.operation.NacosConfigOperation;
import com.easy.mall.operation.NacosSubscribeCallback;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Optional;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @description: 基于Nacos配置中心實(shí)現(xiàn)Gateway 動(dòng)態(tài)路由配置
 * @author: liangbo
 * @create 2020-12-15 19:29
 * @Version 1.0
 **/
@Slf4j
@DependsOn(value= {"gatewayConfig","nacosAutoConfiguration"})
@Configuration
@ConditionalOnProperty(prefix = "global.gateway.dynamicRoute", name = "enabled", havingValue = "true")
public class NacosDynamicRouteDefinitionRepository implements RouteDefinitionRepository {

  @Autowired
  private NacosConfigOperation nacosConfigOperation;

  @Autowired
  private ApplicationEventPublisher publisher;


  @Override
  public Flux<RouteDefinition> getRouteDefinitions() {

    //從Nacos配置中心讀取路由配置
    try {
      String dynamicRouteStr = nacosConfigOperation.getConfig(GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.NACOS_ROUTE_DATA_ID);
      log.info("init dynamicRoute success.:{}", dynamicRouteStr);
      List<RouteDefinition> routeDefinitions = Optional.ofNullable(dynamicRouteStr)
          .map(str -> JSONObject.parseArray(str, RouteDefinition.class))
          .orElse(Lists.newArrayList());
      return Flux.fromIterable(routeDefinitions);
    } catch (NacosException e) {
      log.error("load gateway dynamicRoute config error:{}", e);
    }

    return Flux.fromIterable(Lists.newArrayList());
  }

  @Override
  public Mono<Void> save(Mono<RouteDefinition> route) {
    return null;
  }

  @Override
  public Mono<Void> delete(Mono<String> routeId) {
    return null;
  }

  /**
   * 偵聽(tīng)nacos config 實(shí)時(shí)刷新路由配置
   */
  @PostConstruct
  public void subscribeConfigRefresh()  {
    try {
      nacosConfigOperation.subscribeConfig(GatewayConfig.NACOS_ROUTE_GROUP,
          GatewayConfig.NACOS_ROUTE_DATA_ID, null, new NacosSubscribeCallback () {

            @Override
            public void callback(String config) {
              publisher.publishEvent(new RefreshRoutesEvent(this));
            }
          });
    } catch (NacosException e) {
      log.error("nacos-addListener-error", e);
    }
  }

}

動(dòng)態(tài)路由配置清單

[
   {
      "id": "easy-mall-auth",
      "predicates": [{
          "name": "Path",
          "args": {
              "pattern": "/emallauth/**"
          }
      }],
      "uri": "lb://easy-mall-auth",
      "filters": [{
          "name": "StripPrefix",
          "args": {
              "parts": "1"
          }
      }]
  } 
]

4.2 基于網(wǎng)關(guān)+nacos配置中心實(shí)現(xiàn)灰度路由

實(shí)現(xiàn)思路見(jiàn)Nacos安裝及Spring Cloud 集成 3.4

4.2.1 定義GatewayStrategyAutoConfiguration 網(wǎng)關(guān)路由自定義配置入口類(lèi)

@Configuration
@AutoConfigureBefore(RibbonClientConfiguration.class)
//通過(guò)注解@RibbonClient聲明附加配置,此處聲明的配置會(huì)覆蓋配置文件中的配置
@RibbonClients(defaultConfiguration = { GatewayStrategyLoadBalanceConfiguration.class })
@ConditionalOnProperty(value = StrategyConstant.SPRING_APPLICATION_STRATEGY_CONTROL_ENABLED, matchIfMissing = true)
public class GatewayStrategyAutoConfiguration {
//省略......

通過(guò)入口類(lèi)晰筛,加載自定義全局過(guò)濾器嫡丙、Ribbon自定義負(fù)載均衡配置、元數(shù)據(jù)處理適配器等读第。

自定義Ribbon 負(fù)載均衡實(shí)現(xiàn)
自定義Ribbon 負(fù)載均衡實(shí)現(xiàn)分別對(duì)PredicateBasedRule和ZoneAvoidanceRule進(jìn)行了擴(kuò)展

//通過(guò)注解@RibbonClient聲明附加配置曙博,此處聲明的配置會(huì)覆蓋配置文件中的配置
@RibbonClients(defaultConfiguration = { GatewayStrategyLoadBalanceConfiguration.class })


@Bean
public IRule ribbonRule(IClientConfig config) {

    if (this.propertiesFactory.isSet(IRule.class, serviceId)) {
        return this.propertiesFactory.get(IRule.class, config, serviceId);
    }

    //開(kāi)啟和關(guān)閉Ribbon默認(rèn)的ZoneAvoidanceRule負(fù)載均衡策略。一旦關(guān)閉卦方,則使用RoundRobin簡(jiǎn)單輪詢(xún)負(fù)載均衡策略羊瘩。缺失則默認(rèn)為true
    boolean zoneAvoidanceRuleEnabled = environment.getProperty(StrategyConstant.SPRING_APPLICATION_STRATEGY_ZONE_AVOIDANCE_RULE_ENABLED, Boolean.class, Boolean.TRUE);
    if (zoneAvoidanceRuleEnabled) {
        DiscoveryEnabledZoneAvoidanceRule discoveryEnabledRule = new DiscoveryEnabledZoneAvoidanceRule();
        discoveryEnabledRule.initWithNiwsConfig(config);

        DiscoveryEnabledZoneAvoidancePredicate discoveryEnabledPredicate = discoveryEnabledRule.getDiscoveryEnabledPredicate();
        discoveryEnabledPredicate.setPluginAdapter(pluginAdapter);
        discoveryEnabledPredicate.setDiscoveryEnabledAdapter(discoveryEnabledAdapter);

        return discoveryEnabledRule;
    } else {
        DiscoveryEnabledBaseRule discoveryEnabledRule = new DiscoveryEnabledBaseRule();

        DiscoveryEnabledBasePredicate discoveryEnabledPredicate = discoveryEnabledRule.getDiscoveryEnabledPredicate();
        discoveryEnabledPredicate.setPluginAdapter(pluginAdapter);
        discoveryEnabledPredicate.setDiscoveryEnabledAdapter(discoveryEnabledAdapter);

        return discoveryEnabledRule;
    }
}

DiscoveryEnabledZoneAvoidanceRule:

ZoneAvoidanceRule擴(kuò)展

DiscoveryEnabledBaseRule
PredicateBasedRule

自定義全局過(guò)濾器 實(shí)現(xiàn)將網(wǎng)關(guān)路由配置以及Http Header加載到請(qǐng)求ServerWebExchange中

@Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // 把ServerWebExchange放入ThreadLocal中
    GatewayStrategyContext.getCurrentContext().setExchange(exchange);

    // 通過(guò)過(guò)濾器設(shè)置路由Header頭部信息,并全鏈路傳遞到服務(wù)端
    ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();

    if (gatewayCoreHeaderTransmissionEnabled) {
      // 內(nèi)置Header預(yù)先塞入
      Map<String, String> headerMap = strategyWrapper.getHeaderMap();
      if (MapUtils.isNotEmpty(headerMap)) {
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
          String key = entry.getKey();
          String value = entry.getValue();

          GatewayStrategyFilterResolver.setHeader(requestBuilder, key, value, gatewayHeaderPriority);
        }
      }

      //獲取網(wǎng)關(guān)配置的路由規(guī)則
      String routeVersion = getRouteVersion();
      String routeVersionWeight = getRouteVersionWeight();
      String routeIdBlacklist = getRouteIdBlacklist();
      String routeAddressBlacklist = getRouteAddressBlacklist();

      if (StringUtils.isNotEmpty(routeVersion)) {
        GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_VERSION, routeVersion, gatewayHeaderPriority);
      } else {
        GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);
      }

      if (StringUtils.isNotEmpty(routeVersionWeight)) {
        GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT, routeVersionWeight, gatewayHeaderPriority);
      } else {
        GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);
      }

      if (StringUtils.isNotEmpty(routeIdBlacklist)) {
        GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST, routeIdBlacklist, gatewayHeaderPriority);
      } else {
        GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);
      }
      if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
        GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST, routeAddressBlacklist, gatewayHeaderPriority);
      } else {
        GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);
      }
    } else {
      GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION);
      GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT);
      GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST);
      GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST);
    }

    // 對(duì)于服務(wù)A -> 網(wǎng)關(guān) -> 服務(wù)B調(diào)用鏈
    // 域網(wǎng)關(guān)下(zuulHeaderPriority=true)盼砍,只傳遞網(wǎng)關(guān)自身的group,不傳遞服務(wù)A的group逝她,起到基于組的網(wǎng)關(guān)端服務(wù)調(diào)用隔離
    // 非域網(wǎng)關(guān)下(zuulHeaderPriority=false)浇坐,優(yōu)先傳遞服務(wù)A的group,基于組的網(wǎng)關(guān)端服務(wù)調(diào)用隔離不生效黔宛,但可以實(shí)現(xiàn)基于相關(guān)參數(shù)的熔斷限流等功能
    GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_GROUP, pluginAdapter.getGroup(), gatewayHeaderPriority);
    // 網(wǎng)關(guān)只負(fù)責(zé)傳遞服務(wù)A的相關(guān)參數(shù)(例如:serviceId)近刘,不傳遞自身的參數(shù),實(shí)現(xiàn)基于相關(guān)參數(shù)的熔斷限流等功能
    GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_TYPE, pluginAdapter.getServiceType(), false);
    String serviceAppId = pluginAdapter.getServiceAppId();
    if (StringUtils.isNotEmpty(serviceAppId)) {
      GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_APP_ID, serviceAppId, false);
    }
    GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_ID, pluginAdapter.getServiceId(), false);
    GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_VERSION, pluginAdapter.getVersion(), false);
    GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_ENVIRONMENT, pluginAdapter.getEnvironment(), false);

    ServerHttpRequest newRequest = requestBuilder.build();
    ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

    ServerWebExchange extensionExchange = extendFilter(newExchange, chain);

    ServerWebExchange finalExchange = extensionExchange != null ? extensionExchange : newExchange;

    // 把新的ServerWebExchange放入ThreadLocal中
    GatewayStrategyContext.getCurrentContext().setExchange(newExchange);

    String path = finalExchange.getRequest().getPath().toString();
    if (path.contains(DiscoveryConstant.INSPECTOR_ENDPOINT_URL)) {
      GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.INSPECTOR_ENDPOINT_HEADER, pluginAdapter.getPluginInfo(null), true);
    }

    return chain.filter(finalExchange);
  }

自定義DefaultDiscoveryEnabledAdapter封裝實(shí)例過(guò)濾規(guī)則
Ribbon負(fù)載均衡器執(zhí)行默認(rèn)過(guò)濾后會(huì)執(zhí)行該規(guī)則

  protected boolean apply(Server server) {
        if (discoveryEnabledAdapter == null) {
            return true;
        }

        return discoveryEnabledAdapter.apply(server);
    }
//自定義過(guò)濾規(guī)則
  @Override
    public boolean apply(Server server) {
      boolean enabled = applyEnvironment(server);
      if (!enabled) {
        return false;
      }

      enabled = applyVersion(server);
      if (!enabled) {
        return false;
      }

      enabled = applyIdBlacklist(server);
      if (!enabled) {
        return false;
      }

      enabled = applyAddressBlacklist(server);
      if (!enabled) {
        return false;
      }

      return applyStrategy(server);
    }

4.2.2 網(wǎng)關(guān)路由策略發(fā)布

基于nacos配置實(shí)現(xiàn)網(wǎng)關(guān)策略動(dòng)態(tài)發(fā)布臀晃,根據(jù)網(wǎng)關(guān)元數(shù)據(jù)組以及serviceId創(chuàng)建路由策略配置:


網(wǎng)關(guān)路由策略配置

配置通過(guò)網(wǎng)關(guān)的請(qǐng)求都走版本xx

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <version>1.0</version>
    </strategy>
</rule>

step1 啟動(dòng)網(wǎng)關(guān)以及2個(gè)服務(wù)實(shí)例

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=1100 --spring.cloud.nacos.discovery.metadata.version=1.0"
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=1101 --spring.cloud.nacos.discovery.metadata.version=1.1"
nacos實(shí)例

nacos服務(wù)實(shí)例

step2 通過(guò)網(wǎng)關(guān)調(diào)用服務(wù)觉渴,可以驗(yàn)證到請(qǐng)求始終訪問(wèn)到version為1.0 的服務(wù)實(shí)例

192.168.132.49:1500/nacos-provider/index

配置網(wǎng)關(guān)路由權(quán)重

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <version>1.0;1.1</version>
        <version-weight>1.0=90;1.1=10</version-weight>
    </strategy>
</rule>

灰度策略信息基于Nacos Client以及異步事件處理,動(dòng)態(tài)更新徽惋,無(wú)需重啟網(wǎng)關(guān)案淋。
通過(guò)網(wǎng)關(guān)訪問(wèn)多次服務(wù),請(qǐng)求基本按照9:1的比例命中服務(wù)险绘。

配置IP地址和端口屏蔽策略踢京,實(shí)現(xiàn)服務(wù)流量無(wú)損策略下線
服務(wù)下線場(chǎng)景中,由于Ribbon負(fù)載均衡組件存在著緩存機(jī)制宦棺,當(dāng)被調(diào)用的服務(wù)實(shí)例已經(jīng)下線瓣距,而調(diào)用的服務(wù)實(shí)例還暫時(shí)緩存著它,直到下個(gè)心跳周期才會(huì)把已下線的服務(wù)實(shí)例剔除代咸,在此期間蹈丸,會(huì)造成流量有損
框架提供流量的實(shí)時(shí)性的絕對(duì)無(wú)損。采用下線之前,把服務(wù)實(shí)例添加到屏蔽名單中逻杖,負(fù)載均衡不會(huì)去尋址該服務(wù)實(shí)例奋岁。
代碼清單:

//省略
enabled = applyIdBlacklist(server);
 if (!enabled) {
    return false;
 }
//省略

//過(guò)濾黑名單IP,框架會(huì)將黑名單中IP從Ribbon負(fù)載實(shí)例中移除
public boolean applyIdBlacklist(Server server) {
      String ids = pluginContextHolder.getContextRouteIdBlacklist();
      if (StringUtils.isEmpty(ids)) {
        return true;
      }

      String serviceUUId = pluginAdapter.getServerServiceUUId(server);

      List<String> idList = StringUtil.splitToList(ids, DiscoveryConstant.SEPARATE);
      if (idList.contains(serviceUUId)) {
        return false;
      }

      return true;
    }

配置Ip黑名單

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    //此處省略
    <strategy-blacklist>
        <!-- 單個(gè)Address形式弧腥。如果多個(gè)用“;”分隔厦取,不允許出現(xiàn)空格 -->
        <address value="192.168.132.49:1100"/>
    </strategy-blacklist>
</rule>

發(fā)布配置后,訪問(wèn)服務(wù)可以發(fā)現(xiàn)請(qǐng)求屏蔽了端口為1100的服務(wù)管搪,確保1100服務(wù)下線虾攻,請(qǐng)求不會(huì)命中到1100服務(wù)。
源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末更鲁,一起剝皮案震驚了整個(gè)濱河市霎箍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澡为,老刑警劉巖漂坏,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異媒至,居然都是意外死亡顶别,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)拒啰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)驯绎,“玉大人,你說(shuō)我怎么就攤上這事谋旦∈JВ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵册着,是天一觀的道長(zhǎng)拴孤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)甲捏,這世上最難降的妖魔是什么演熟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮摊鸡,結(jié)果婚禮上绽媒,老公的妹妹穿的比我還像新娘。我一直安慰自己免猾,他們只是感情好是辕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著猎提,像睡著了一般获三。 火紅的嫁衣襯著肌膚如雪旁蔼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天疙教,我揣著相機(jī)與錄音棺聊,去河邊找鬼。 笑死贞谓,一個(gè)胖子當(dāng)著我的面吹牛限佩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裸弦,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼祟同,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了理疙?” 一聲冷哼從身側(cè)響起晕城,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窖贤,沒(méi)想到半個(gè)月后砖顷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滤蝠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年授嘀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粤攒。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夯接,死狀恐怖纷妆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掩幢,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布芯丧,位于F島的核電站,受9級(jí)特大地震影響世曾,放射性物質(zhì)發(fā)生泄漏缨恒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骗露。 院中可真熱鬧岭佳,春花似錦、人聲如沸萧锉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柿隙。三九已至叶洞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間优俘,已是汗流浹背京办。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帆焕,地道東北人惭婿。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像叶雹,于是被迫代替她去往敵國(guó)和親财饥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354