⑥SpringCloud 實戰:引入gateway組件,開啓網關路由功能

這是SpringCloud實戰系列中第4篇文章,瞭解前面第兩篇文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
②SpringCloud 實戰:引入Feign組件,發起服務間調用
③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡
④SpringCloud 實戰:引入Hystrix組件,分佈式系統容錯
⑤SpringCloud 實戰:引入Zuul組件,開啓網關路由html

簡介

Spring Cloud Gateway 是 Spring Cloud 的一個子項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在爲微服務架構提供一種簡單有效的統一的 API 路由管理方式。java

Spring Cloud Gateway 做爲 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不只提供統一的路由方式,而且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。react

特性

Spring Cloud Gateway 具備以下特性:web

  • 基於Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構建;
  • 動態路由:可以匹配任何請求屬性;
  • 能夠對路由指定 Predicate(斷言)和 Filter(過濾器);
  • 集成Hystrix的斷路器功能;
  • 集成 Spring Cloud 服務發現功能;
  • 易於編寫的 Predicate(斷言)和 Filter(過濾器);
  • 請求限流功能;
  • 支持路徑重寫。

實戰 Gateway

引入 gateway

  1. 新建jlw-gateway項目正則表達式

  2. 引入gateway依賴redis

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
  3. 加入Eureka註冊中心算法

  4. 開啓服務註冊和發現spring

    # gateway 服務端口
    server:
        port: 9000
    
    spring:
        cloud:
            gateway:
                discovery:
                    locator:
                        # 啓用DiscoveryClient網關集成的標誌
                        enabled: true
                        # 服務小寫匹配
                        lower-case-service-id: true

    配置完上面,Gateway 就能夠自動根據服務發現爲每一個服務建立router了,而後將已服務名開頭的請求路徑轉發到對應的服務。api

Gateway Actuator API

pom中引入spring-boot-starter-actuator相關依賴,而後配置文件添加以下代碼,開啓gateway相關的端點。跨域

management:
    endpoints:
        web:
            exposure:
                #應該包含的端點ID,所有:*
                include:  'gateway'

重啓項目,訪問http://localhost:9000/actuator/gateway/routes就能夠查看到配置的路由信息了

更多gateway路由信息接口展現以下圖:

相關概念

  • Route(路由):
    路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,若是斷言爲true則匹配該路由;
  • Predicate(斷言):
    指的是Java 8 的 Function Predicate。 輸入類型是Spring框架中的ServerWebExchange。 這使開發人員能夠匹配HTTP請求中的全部內容,例如請求頭或請求參數。若是請求與斷言相匹配,則進行路由;
  • Filter(過濾器):
    指的是Spring框架中GatewayFilter的實例,使用過濾器,能夠在請求被路由先後對請求進行修改。

路由基本配置

Gateway 提供了兩種不一樣的方式用於配置路由:一種是經過yml文件來配置,另外一種是經過Java Bean來配置

使用配置文件

spring:
    cloud:
        gateway:
            routes:
                - id: eureka-provider
                  uri: lb://eureka-provider
                  predicates:
                    - Path=/api/ep/**
                  filters:
                    - StripPrefix=2

字段含義解釋:

  • id
    咱們自定義的路由 ID,保持惟一
  • uri
    目標服務地址,大部分場景咱們是轉發到某個服務上,配置lb://eureka-provider意思是請求要轉發到註冊中心的eureka-provider服務上。
  • predicates
    路由條件,接受一個參數,返回一個布爾結果決定是否匹配。Gateway 爲咱們內置了多種路由條件,包括 Path、Cookie、Param、Header、Before、After 等等,開箱即用,固然咱們也能夠本身實現 predicates
  • filters
    過濾規則,當請求通過 predicate 匹配成功後,執行 filter,咱們可使用它修改請求和響應,示例表示目標服務收到的 path 將會忽略2位路徑path。

②使用Java Bean配置

配置RouteLocator對象,代碼示例以下:

@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
    return builder.routes()
            // 請求網關路徑包含 /api/ec/** 的都會被路由到eureka-client
            .route("eureka-client",r->r.path("/api/ec/**")
                    .filters(f->f.stripPrefix(2))
                    .uri("lb://eureka-client"))
            // 能夠配置多個route
            .route("eureka-client2",r->r.path("/api/ec2/**")
                    .filters(f->f.stripPrefix(2))
                    .uri("lb://eureka-client"))
            .build();
}

以上配置後,經過http://localhost:9000/api/ec/sayHello 或者http://localhost:9000/api/ec2/sayHello 都會被路由到eureka-client服務。

路由規則:Predicate

Spring Cloud Gateway 內置了不少 Predicates 工廠(能夠經過訪問路徑/actuator/gateway/routepredicates查看),這些 Predicates 工廠經過不一樣的 HTTP 請求參數來匹配,多個 Predicates 工廠能夠組合使用。

按照其功能能夠大體分爲如下幾種不一樣 Predicate

1.經過請求時間匹配路由

  1. 指定時間以後的請求會匹配該路由:AfterRoutePredicateFactory

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - After=2019-10-10T00:00:00+08:00[Asia/Shanghai]
  2. 指定時間以前的請求會匹配該路由:BeforeRoutePredicateFactory

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - Before=2019-10-10T00:00:00+08:00[Asia/Shanghai]
  3. 指定時間區間內的請求會匹配該路由:BetweenRoutePredicateFactory

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - Between=2019-10-01T00:00:00+08:00[Asia/Shanghai], 2019-10-10T00:00:00+08:00[Asia/Shanghai]

2.經過Cookie匹配路由

Cookie Route Predicate 能夠接收兩個參數,一個是 Cookie name,一個是正則表達式。示例以下:

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates: 
                      - Cookie=name, jinglingwang.cn

3.經過 Header匹配路由

Header Route Predicate 和 Cookie Route Predicate 同樣,也是接收 2 個參數,一個 header 中屬性名稱和一個正則表達式,示例以下:

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates:
                      - Header=name, jinglingwang.cn

4.經過 Host 匹配路由

該模式接收一個參數:主機列表,示例以下:

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates:
                      - Host=**.jinglingwang.cn,**.jinglingwang.com

5.經過 Request Method 匹配路由

能夠經過是 POST、GET、PUT、DELETE 等不一樣的請求方式來進行路由,示例代碼以下:

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates:
                      - Method=GET,POST

6.經過請求路徑匹配路由

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates: 
                      - Path=/api/rc/**

7.經過請求參數匹配路由

該模式有兩個參數:一個必需的param和一個可選的regexp(Java正則表達式),示例以下

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates:
                      - QUERY=name,jingling*

8.經過指定遠程地址匹配路由

spring:
    cloud:
        gateway:
            routes:
                - id: ribbon-client
                  uri: lb://ribbon-client
                  predicates:
                      - RemoteAddr=192.168.1.1/24 #192.168.1.1是IP  24是子網掩碼

若是請求的遠程地址是192.168.1.10,則此路由匹配。

9.經過權重來匹配路由

該模式有兩個參數:group和Weight(一個int值),示例以下:

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: http://localhost:8201
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: http://localhost:8202
        predicates:
        - Weight=group1, 2

以上表示有80%的請求會被路由到localhost:8201,20%會被路由到localhost:8202

網關過濾器

路由過濾器容許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器只能指定路由進行使用。Spring Cloud Gateway 內置了多種路由過濾器,他們都由GatewayFilter的工廠類來產生。

  1. 添加請求Header過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      filters:
                        - AddRequestHeader=source, jinglingwang.cn

    上面的示例會爲全部匹配的請求向下遊請求時在Header中添加source=jinglingwang.cn

  2. 添加請求參數過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      filters:
                        - AddRequestParameter=source, jinglingwang.cn

    上面的示例會把source=jinglingwang.cn添加到下游的請求參數中

  3. 添加響應頭過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      filters:
                        - AddResponseHeader=source, jinglingwang.cn

    上面的示例會把source=jinglingwang.cn添加到全部匹配請求的下游響應頭中。

  4. 剔除重複的響應頭過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      filters:
                        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

    DedupeResponseHeader過濾器也可接收可選策略參數,可接收參數值包括:RETAIN_FIRST (默認值,保留第一個值), RETAIN_LAST(保留最後一個值), and RETAIN_UNIQUE(保留全部惟一值,以它們第一次出現的順序保留)。

  5. 開啓Hystrix斷路器功能的過濾器

    要開啓斷路器功能,咱們須要在pom.xml中添加Hystrix的相關依賴:spring-cloud-starter-netflix-hystrix

    而後添加相關服務降級的處理類:

    @RestController
    public class FallbackController{
        @GetMapping("/fallback")
        public Object fallback() {
            
            Map<String,Object> result = new HashMap<>(3);
            result.put("data","jinglingwang.cn");
            result.put("message","Get request fallback!");
            result.put("code",500);
            return result;
        }
    }

    添加配置

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                        - name: Hystrix
                          args:
                            name: fallbackcmd
                            fallbackUri: forward:/fallback
  6. 啓用resilience4j斷路器的過濾器

    要啓用Spring Cloud斷路器過濾器,須要引入依賴spring-cloud-starter-circuitbreaker-reactor-resilience4j

    spring:
    	  cloud:
    		gateway:
    			 routes:
    				- id: ribbon-client
    				  uri: lb://ribbon-client
    				  filters:
    				   - CircuitBreaker=myCircuitBreaker

    還能夠接受一個可選的fallbackUri參數:

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                        - name: CircuitBreaker
                          args:
                            name: myCircuitBreaker
                            fallbackUri: forward:/fallback

    關閉eureka-provider服務,訪問http://localhost:9000/api/ep/hello接口,出現降級處理信息

    上面的配置也能夠用JAVA Bean的方式配置:

    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route("eureka-provider", r -> r.path("/api/ep/**")
                        .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback")))
                        .uri("lb://eureka-provider"))
                .build();
    }

    6.1 根據狀態碼使斷路器跳閘

    根據返回的狀態碼,決定斷路器是否要跳閘

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                        - name: CircuitBreaker
                          args:
                            name: myCircuitBreaker
                            fallbackUri: forward:/fallback
                            statusCodes:
                              - 500
                              - 'NOT_FOUND'

    或者JAVA Bean配置:

    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route("eureka-provider", r -> r.path("/api/ep/**")
                        .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500")))
                        .uri("lb://eureka-provider"))
                .build();
    }

    其中NOT_FOUND是HttpStatus枚舉的String表示形式

  7. 增長路徑的過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - PrefixPath=/mypath

    /mypath做爲全部匹配請求路徑的前綴

  8. 去掉路徑前綴的過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2

    以上配置會忽略兩位路徑path,當訪問網關API /api/ep/hello 時,向eureka-provider發起/hello請求

  9. 用於限流的過濾器

    RequestRateLimiter 過濾器能夠用於限流,RateLimiter實現來肯定是否容許繼續當前請求。 若是不是,則返回HTTP 429—太多請求(默認)的狀態。

    該過濾器採用可選的keyResolver參數和速率限制器特定的參數,keyResolver是一個實現KeyResolver接口的bean。在配置中,使用SpEL按名稱引用bean。#{@myKeyResolver}是引用名爲myKeyResolver的bean的SpEL表達式。

    使用 Redis RateLimiter

    1. 引入redis依賴,配置好redis。

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
      </dependency>
    2. 添加配置,使用的算法是令牌桶算法。

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - name: RequestRateLimiter
                            args:
                              redis-rate-limiter.replenishRate: 10  #容許用戶每秒處理多少個請求,而不丟棄任何請求。這是令牌桶的填充速率
                              redis-rate-limiter.burstCapacity: 20  #一個用戶在一秒鐘內容許作的最大請求數。這是令牌桶能夠容納的令牌數。將該值設置爲零會阻止全部請求。
                              redis-rate-limiter.requestedTokens: 1 #一個請求花費多少令牌

      經過在「replenishRate」和「burstCapacity」中設置相同的值來實現穩定的速率。經過將burstCapacity設置爲高於replenishRate,能夠容許臨時爆發。

    3. 配置KeyResolver

      JAVA 代碼:

      @Configuration
      public class RedisRateLimiterConfig{
          @Bean
          KeyResolver userKeyResolver() {
              // 根據請求參數中的phone進行限流
              return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("phone"));
          }
          @Bean
          @Primary
          public KeyResolver ipKeyResolver() {
              // 根據訪問IP進行限流
              return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
          }
      }
    4. 配置RequestRateLimiter

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - name: RequestRateLimiter
                            args:
                              redis-rate-limiter.replenishRate: 1  #容許用戶每秒處理多少個請求,而不丟棄任何請求。這是令牌桶的填充速率
                              redis-rate-limiter.burstCapacity: 2  #一個用戶在一秒鐘內容許作的最大請求數。這是令牌桶能夠容納的令牌數。將該值設置爲零會阻止全部請求。
                              redis-rate-limiter.requestedTokens: 1 #一個請求花費多少令牌
                              key-resolver: "#{@ipKeyResolver}"

      屢次請求,會返回狀態碼爲429的錯誤

  10. 用於重定向的過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                        - RedirectTo=302, https://jinglingwang.cn  #302 重定向到https://jinglingwang.cn

    效果圖以下:

  11. 用於重試的過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                        - name: Retry
    		  args:
    		    retries: 3
    		    statuses: BAD_GATEWAY
    		    methods: GET,POST
    		    backoff:
    			 firstBackoff: 10ms
    			 maxBackoff: 50ms
    			 factor: 2
    			 basedOnPreviousValue: false

    參數解釋:

    • retries:應該嘗試的重試次數。
    • statuses:應該重試的HTTP狀態代碼,HttpStatus。
    • methods:應該重試的HTTP方法,HttpMethod。
    • series:要重試的狀態碼,Series。
    • exceptions:應該重試的引起異常的列表。
    • backoff:爲重試配置的指數補償
  12. 用於限制請求大小的過濾器

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
                       - name: RequestSize
                         args:
    			     maxSize: 5MB

    當請求大小大於容許的限制時,網關會限制請求到達下游服務。maxSize參數後跟一個可選的數據單位,如「KB節」或「MB」,默認是「B」。

    上面的配置若是超過限制會出現如下提示:

    errorMessage:Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB

配置http超時時間

咱們能夠爲全部路由配置Http超時(響應和鏈接),而且爲每一個特定路由配置單獨的超時時間

全局的超時時間配置:

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s

特定路由配置超時時間

spring:
    cloud:
        gateway:
            routes:
                - id: eureka-provider
                  uri: lb://eureka-provider
                  predicates:
                    - Path=/api/ep/**
                  metadata:
                     response-timeout: 2000
                     connect-timeout: 1000

或者使用JAVA Bean的方式配置:

@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
    return builder.routes()
            .route("eureka-provider", r -> r.path("/api/ep/**")
                    .filters(f->f.stripPrefix(2)
                            .requestRateLimiter(rate->rate.setKeyResolver(ipKeyResolver))
                            .circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500").addStatusCode("NOT_FOUND")))
                    .uri("lb://eureka-provider")
                    .metadata(RESPONSE_TIMEOUT_ATTR, 200)
                    .metadata(CONNECT_TIMEOUT_ATTR, 200))
            .build();
}

跨域配置

spring:
    cloud:
        gateway:
            globalcors: # 全局跨域配置
              cors-configurations:
                '[/**]':
                  allowedOrigins: "jinglingwang.cn"
                  allowedMethods:
                    - GET
              add-to-simple-url-handler-mapping: true

上面的配置,對於全部Get請求,容許來自jinglingwang.cn的跨域請求。

自定義 Route Predicate Factories

經過一個名爲AbstractRoutePredicateFactory的抽象類來進行擴展,示例代碼:

public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    public MyRoutePredicateFactory(){
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config){
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                String host = exchange.getRequest().getHeaders().getFirst("Host");
                return "jinglingwang.cn".equalsIgnoreCase(host);
            }

            @Override
            public String toString() {
                return String.format("host: name=%s ", config.host);
            }
        };
    }

    public static class Config {
        //自定義過濾器的配置屬性
        @NotEmpty
        private String host;
    }

}

自定義 GatewayFilter Factories

要寫GatewayFilter,必須實現GatewayFilterFactory,能夠經過擴展一個名爲AbstractGatewayFilterFactory的抽象類來進行。

@Component
public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config>{

    public AddHeaderGatewayFilterFactory() {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config){
        return (exchange, chain) -> {
            // 若是要構建「前置」過濾器,則須要在調用chain.filter以前處理
            ServerHttpRequest request = exchange.getRequest().mutate()
                    .header("source", "jinglingwang.cn").build();
            //使用構建器來處理請求
            return chain.filter(exchange.mutate().request(request).build());
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }
}

配置屬性

要查看全部與 Spring Cloud 網關相關的配置屬性列表,請參見附錄

相關文章
相關標籤/搜索