中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

第二代微服務網關組件 - Spring Cloud Gateway

發布時間:2020-06-19 01:10:07 來源:網絡 閱讀:3356 作者:ZeroOne01 欄目:編程語言

[TOC]


初識Spring Cloud Gateway

簡介:

Spring Cloud Gateway是Spring Cloud體系的第二代網關組件,基于Spring 5.0的新特性WebFlux進行開發,底層網絡通信框架使用的是Netty,所以其吞吐量高、性能強勁,未來將會取代第一代的網關組件Zuul。Spring Cloud Gateway可以通過服務發現組件自動轉發請求,默認集成了Ribbon做負載均衡,以及默認使用Hystrix對網關進行保護,當然也可以選擇其他的容錯組件,例如Sentinel

優點:

  • 性能強勁:是第一代網關Zuul的1.6倍
  • 功能強大:內置了很多實用的功能,例如轉發、監控、限流等
  • 設計優雅,容易擴展

缺點:

  • 其實現依賴Netty與WebFlux,不是傳統的Servlet編程模型,有一定的學習成本
  • 不能在Servlet容器下工作,也不能構建成WAR包,即不能將其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包執行
  • 不支持Spring Boot 1.x,需2.0及更高的版本

如果對網關概念或Zuul不了解的話,可以參考另一篇文章:

  • Spring Cloud Zuul 快速入門

核心概念:

1、Route(路由):

Spring Cloud Gateway的基礎元素,可簡單理解成一條轉發規則。包含:ID、目標URL、Predicate集合以及Filter集合

這是一段比較典型的Gateway路由配置:

spring:
  cloud:
    gateway:
      routes:
      - id: user-center  # 唯一標識,通常使用服務id
        uri: lb://user-center  # 目標URL,lb代表從注冊中心獲取服務,lb是Load Balance的縮寫
        predicates:
        # Predicate集合
        - Path=/zj/cloud/v1/user-center/**  # 匹配轉發路徑
        filters:
        # Filter集合
        - StripPrefix=4  # 從第幾級開始轉發

2、Predicate(謂詞):

java.util.function.Predicate這個接口,Gateway使用Predicate實現路由的匹配條件

3、Filter(過濾器):

與我們平時使用的Servlet編程模型里的過濾器概念類似,同樣可以用于修改請求以及響應數據,可以利用Filter實現鑒權、訪問日志記錄,接口耗時記錄等功能

Spring Cloud Gateway架構圖:
第二代微服務網關組件 - Spring Cloud Gateway

  • 圖片來自官方文檔

簡單解讀一下這個圖:

Gateway Client發送請求給Spring Cloud Gateway,Gateway Handler Mapping會判斷請求的路徑是否匹配路由的配置,如果匹配則會進入Gateway Web Handler,Web Handler會讀取路由上所配置的過濾器,然后將該請求交給過濾器去處理,最后轉發到路由配置的微服務上

  • Gateway Client:泛指外部請求,例如瀏覽器、app、小程序等
  • Proxied Service:指的是被網關代理的微服務

相關源碼:

  • Gateway Handler Mapping:org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping
  • Gateway Web Handler:org.springframework.cloud.gateway.handler.FilteringWebHandler

由于Webflux大量運用函數式編程思想,所以本文中的示例代碼都會使用lambda表達式及函數式API來簡化。若對此不了解的話,可以參考相關文章,篇幅有限這里就不進行介紹了:

  • Java函數式編程和lambda表達式
  • Spring船新版推出的WebFlux,是兄弟就來學我

創建Spring Cloud Gateway項目

這里使用IDEA的Spring Initializr進行項目的創建,到選擇依賴這一步勾選gateway依賴,如下圖:
第二代微服務網關組件 - Spring Cloud Gateway

網關組件一般都配合服務發現組件使用,我這里使用Nacos作為服務發現組件,具體的依賴如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- Nacos Client -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- actuator -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <!--整合Spring Cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--整合Spring Cloud Alibaba-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

如果對Nacos不熟悉的話可以參考另一篇關于Nacos的文章,或者采用Eureka也是一樣的:

  • Spring Cloud Alibaba之服務發現組件 - Nacos

然后編寫配置文件內容如下:

server:
  port: 8040
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          # 讓gateway通過服務發現組件找到其他的微服務,從而自動轉發請求
          enabled: true

# actuator相關配置
management:
  endpoints:
    web:
      exposure:
        # 暴露所有監控端點
        include: '*'
  endpoint:
    health:
      # 總是顯示健康檢測詳情
      show-details: always

完成以上步驟后,我們來啟動這個網關服務,進行一個簡單的測試,看看是否能將請求正常地轉發到指定的微服務上。此時有一個名為user-center的微服務,該微服務有一個按id獲取用戶信息的接口,接口路徑為/users/{id}。若通過網關服務來訪問這個接口,要如何做呢?很簡單,gateway配合服務發現組件使用時,會有一個默認的轉發規則,如下:

  • ${GATEWAY_URL}/{微服務名稱}/{接口路徑}

所以按該規則得出來的具體url為:localhost:8040/user-center/users/{id},訪問結果如下:
第二代微服務網關組件 - Spring Cloud Gateway

從測試結果可以看到,gateway可以根據url上的微服務名稱將訪問請求轉發到該微服務上。

以上這種是Gateway最簡單的使用方式,但通常在實際開發中,可能不希望使用默認的轉發規則,因為這種方式不太靈活,例如一些服務接口是存在版本劃分的,需要根據不同版本的訪問路徑轉發到不同版本的微服務上。此時就需要自定義轉發路由,實際上在第一小節的時候就已經給出過配置示例了。修改配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: user-center  # 唯一標識,通常使用服務id
        uri: lb://user-center  # 目標URL,lb代表從注冊中心獲取服務
        predicates:
        # Predicate集合
        - Path=/zj/cloud/v1/user-center/**  # 匹配轉發路徑
        filters:
        # Filter集合
        - StripPrefix=4  # 從第幾級開始轉發,數字從0開始

自定義路由的注意事項:

  • predicates配置項必須有,且必須配置一個及以上的Predicate,但不一定非要配置Path,可以配置其他的Predicate,例如AfterBefore等,此時Path的默認值為/**

重啟項目,此時訪問的url為:localhost:8040/zj/cloud/v1/user-center/users/{id},訪問結果如下:
第二代微服務網關組件 - Spring Cloud Gateway


路由配置的兩種形式

Spring Cloud Gateway的路由配置有兩種形式,分別是路由到指定的URL以及路由到指定的微服務,在上一小節的示例中我們就已經使用過路由到微服務的這種配置形式了。在這兩種形式中,均支持訪問路徑的通配及精確匹配,在之前的示例中我們只使用了通配。所以本小節將給出具體的配置示例,以此直觀的了解這兩種形式及不同匹配方式在配置上的區別。

1、路由到指定的URL

通配,使用通配符/**進行匹配,示例:

spring:
  cloud:
    gateway:
      routes:
        - id: test_route  # 路由的唯一標識
          uri: http://www.xxx.com
          predicates:
            # 使用通配符匹配
            - Path=/**
  • 該配置使訪問 GATEWAY_URL/** 時會轉發到 http://www.xxx.com/**

精確匹配,配置具體的接口路徑即可,示例:

spring:
  cloud:
    gateway:
      routes:
        - id: test_route  # 路由的唯一標識
          uri: http://www.xxx.com/user/order/detail
          predicates:
            # 指定具體的路徑進行匹配
            - Path=/user/order/detail
  • 該配置使訪問 GATEWAY_URL/user/order/detail 時會轉發到 http://www.xxx.com/user/order/detail
2、路由到指定的微服務

通配,示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user-center  # 路由的唯一標識,這種形式下通常是微服務名稱
          uri: lb://user-center  # lb代表從注冊中心獲取服務
          predicates:
            # 使用通配符匹配
            - Path=/**
  • 該配置使訪問 GATEWAY_URL/** 時會轉發到 user-center微服務的/**

精確匹配,示例:

spring:
  cloud:
    gateway:
      routes:
        - id: user-center  # 路由的唯一標識,這種形式下通常是微服務名稱
          uri: lb://user-center/users/info  # lb代表從注冊中心獲取服務
          predicates:
            # 指定具體的路徑進行匹配
            - Path=/users/info
  • 該配置使訪問 GATEWAY_URL/users/info 時會轉發到 user-center微服務的/users/info

路由謂詞工廠

前面提到過謂詞是路由的判斷條件,而路由謂詞工廠就是作用到指定路由上的一堆謂詞判斷條件。在之前的示例里,我們就已經使用過路由謂詞工廠了,就是自定義轉發路徑時所配置的Path。

內置的路由謂詞工廠

Spring Cloud Gateway內置了眾多路由謂詞工廠,這些路由謂詞工廠為路由匹配的判斷提供了有力的支持,而我們之前所使用的Path就是內置的路由謂詞工廠之一,用于判斷當前訪問的接口路徑是否與該路由所配置的路徑相匹配,若匹配則進行轉發。由于Gateway內置的路由謂詞工廠比較多,篇幅有限就不在本文中介紹了,可以參考另一篇文章:

  • Spring Cloud Gateway 內置的路由謂詞工廠
自定義路由謂詞工廠

現在我們已經知道Spring Cloud Gateway內置了一系列的路由謂詞工廠,但如果這些內置的路由謂詞工廠不能滿足業務需求的話,我們可以自定義路由謂詞工廠來實現特定的需求。例如有某個服務限制用戶只允許在09:00 - 17:00這個時間段內才可以訪問,內置的路由謂詞工廠是無法滿足這個需求的,所以此時我們就需要自定義能夠實現該需求的路由謂詞工廠。

首先定義一個配置類,用于承載時間段的配置參數:

@Data
public class TimeBetweenConfig {
    /**
     * 開始時間
     */
    private LocalTime start;

    /**
     * 結束時間
     */
    private LocalTime end;
}

然后定義一個路由謂詞工廠,具體代碼如下:

package com.zj.node.gateway.predicate;

import com.zj.node.gateway.config.TimeBetweenConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * 路由謂詞工廠必須以RoutePredicateFactory結尾,
 * 這是Spring Cloud Gateway的約定
 *
 * @author 01
 * @date 2019-08-14
 **/
@Slf4j
@Component
public class TimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TimeBetweenConfig> {

    public TimeBetweenRoutePredicateFactory() {
        super(TimeBetweenConfig.class);
    }

    /**
     * 實現謂詞判斷的方法
     */
    @Override
    public Predicate<ServerWebExchange> apply(TimeBetweenConfig config) {
        return exchange -> {
            LocalTime start = config.getStart();
            LocalTime end = config.getEnd();

            // 判斷當前時間是否為允許訪問的時間段內
            LocalTime now = LocalTime.now();
            return now.isAfter(start) && now.isBefore(end);
        };
    }

    /**
     * 控制配置類(TimeBetweenConfig)屬性和配置文件中配置項(TimeBetween)的映射關系
     */
    @Override
    public List<String> shortcutFieldOrder() {
        /*
         * 例如我們的配置項是:TimeBetween=上午9:00, 下午5:00
         * 那么按照順序,start對應的是上午9:00;end對應的是下午5:00
         **/
        return Arrays.asList("start", "end");
    }
}

最后需要在配置文件中啟用該路由謂詞工廠,并且需要禁止gateway通過服務發現組件轉發請求到其他的微服務,修改Gateway相關配置如下:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 禁止gateway通過服務發現組件轉發請求到其他的微服務
          enabled: false
      routes:
        - id: user-center
          # 目標URL,lb代表從注冊中心獲取服務
          uri: lb://user-center
          predicates:
            # 注意名稱必須為路由謂詞工廠類名的前綴,參數為允許訪問的時間段
            - TimeBetween=上午9:00,下午5:00

可以看到這里主要是配置了我們自定義的路由謂詞工廠類名的前綴以及允許訪問的時間段,這個時間格式不是隨便配置的,而是Spring Cloud Gateway的默認時間格式,相關源碼如下:

  • org.springframework.format.support.DefaultFormattingConversionService#addDefaultFormatters

時間格式是可以注冊的,關于時間格式注冊的相關源碼如下:

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar#registerFormatters

另外,這里之所以要禁止gateway通過服務發現組件轉發請求到其他的微服務,是因為開啟該配置項的話會導致我們自定義的路由謂詞工廠不生效。不生效也是有原因的,開啟該配置項會令Gateway優先將請求按照該配置項進行轉發,那么我們自定義的路由就不會生效。

到此為止我們就實現了一個自定義路由謂詞工廠,若此時不在允許的訪問時間段內,訪問就會報404,如下:
第二代微服務網關組件 - Spring Cloud Gateway


過濾器工廠

前面提到了過濾器可以為請求和響應添加一些業務邏輯或者修改請求和響應對象等,適當地使用過濾器可以讓我們的工作事半功倍,而本小節將要介紹的過濾器工廠就是用來創建過濾器的。在此之前我們已經學習過路由謂詞工廠了,而過濾器工廠與路由謂詞工廠在使用上是類似的,只不過實現的功能不一樣。

內置的過濾器工廠

同樣的Spring Cloud Gateway內置了非常多的過濾器工廠,有二十多個。通過這些內置的過濾器工廠就已經可以靈活且方便地處理請求和響應數據,由于Gateway內置的過濾器工廠實在太多,而篇幅有限就不在本文中介紹了,可以參考另一篇文章:

  • Spring Cloud Gateway 內置的過濾器工廠
自定義過濾器工廠

若Spring Cloud Gateway內置的過濾器工廠無法滿足我們的業務需求,那么此時就需要自定義自己的過濾器工廠以實現特定功能。所謂過濾器工廠實際上就是用于創建過濾器實例的,而創建的過濾器實例都實現于GatewayFilter接口。

過濾器的生命周期:

  • Gateway以轉發請求為邊界,所以其生命周期只包含pre和post:
    • pre:Gateway轉發請求之前
    • post:Gateway轉發請求之后

自定義過濾器工廠的方式:

  1. 繼承AbstractGatewayFilterFactory,參考源碼:org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory。使用該方式實現的過濾器工廠的配置形式如下:

    spring:
    cloud:
    gateway:
      routes:
        filters:
        # 過濾器工廠的名稱
        - name: RequestSize
          # 該過濾器工廠的參數
          args:
            maxSize: 500000
  2. 繼承AbstractNameValueGatewayFilterFactory,參考源碼:org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory。使用該方式實現的過濾器工廠的配置形式如下:
    spring:
    cloud:
    gateway:
      routes:
        filters:
        # 過濾器工廠的名稱及參數以name-value的形式配置
        - AddRequestHeader=S-Header, Bar

注:AbstractNameValueGatewayFilterFactory繼承了AbstractGatewayFilterFactory,所以實際上第二種方式是第一種方式的簡化

核心API:

  • exchange.getRequest().mutate().xxx:修改request
  • exchange.mutate().xxx:修改exchange
  • chain.filter(exchange):傳遞給下一個過濾器處理
  • exchange.getResponse():獲取響應對象

注:這里的exchange實際類型為ServerWebExchangechain實際類型為GatewayFilter

最后我們來實際動手編寫一個自定義過濾器工廠,需求是記錄訪問日志,這里為了簡單起見采用第二種方式實現,具體代碼如下:

package com.zj.node.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

/**
 * 過濾器工廠必須以GatewayFilterFactory結尾,
 * 這是Spring Cloud Gateway的約定
 *
 * @author 01
 * @date 2019-08-15
 **/
@Slf4j
@Component
public class PreLogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

    @Override
    public GatewayFilter apply(NameValueConfig config) {
        // 使用lambda表達式來創建GatewayFilter的實例,實際就是匿名內部類的簡寫
        return (exchange, chain) -> {
            // 通過config獲取配置的參數
            log.info("配置參數:{}, {}", config.getName(), config.getValue());

            // 修改request,可以添加一些header什么的
            ServerHttpRequest modifiedRequest = exchange.getRequest()
                    .mutate()
                    .header("X-GatewayHeader","A","B")
                    .build();

            // 打印訪問的接口地址
            String path = modifiedRequest.getURI().getPath();
            log.info("訪問的接口為:{}", path);

            // 修改exchange
            ServerWebExchange modifiedExchange = exchange.mutate()
                    .request(modifiedRequest).build();

            // 傳遞給下一個過濾器處理
            return chain.filter(modifiedExchange);
        };
    }
}

最后需要添加相關配置以啟用這個過濾器工廠,如下:

spring:
  cloud:
    gateway:
      routes:
        - id: user-center
          uri: lb://user-center
          predicates:
            - TimeBetween=上午9:00,下午5:00
          filters:
            # 名稱必須為過濾器工廠類名的前綴,并且參數只能有兩個,因為NameValueConfig里只定義了兩個屬性
            - PreLog=testName,testValue

啟動項目,訪問user-center的接口,此時控制臺輸出的日志如下:
第二代微服務網關組件 - Spring Cloud Gateway


全局過濾器

現在我們已經知道前面所介紹的過濾器工廠實際用于創建GatewayFilter實例,并且這些GatewayFilter實例僅作用于指定的路由上,那么有沒有可以作用于全部路由上的過濾器呢?答案是有的,這就是本小節將要介紹的全局過濾器。Spring Cloud Gateway默認就內置了許多全局過濾器,本文僅介紹如何自定義全局過濾器,關于Gateway內置的過濾器可以參考另一篇文章:

  • Spring Cloud Gateway 內置的全局過濾器

自定義全局過濾需要實現GlobalFilter 接口,該接口和 GatewayFilter 有一樣的方法定義,只不過 GlobalFilter 的實例會作用于所有的路由。

Tips:

官方聲明:GlobalFilter的接口定義以及用法在未來的版本可能會發生變化。

個人判斷:GlobalFilter可用于生產;如果有自定義GlobalFilter的需求,理論上也可放心使用。因為未來即使接口定義以及使用方式發生變化,理應也是平滑過渡的(比如Zuul的Fallback,原先叫ZuulFallbackProvider,后來改叫FallbackProvider,中間就有段時間新舊使用方式都支持,后面才逐步廢棄老的使用方式)。

接下來我們自定義一個全局過濾器,需求是打印訪問的接口路徑以及打印該接口的訪問耗時。具體代碼如下:

package com.zj.node.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定義全局過濾器
 *
 * @author 01
 * @date 2019-08-17
 **/
@Slf4j
public class MyGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("[MyGlobalFilter] 訪問的接口:{}", path);

        long start = System.currentTimeMillis();
        return chain.filter(exchange)
                // then的內容會在過濾器返回的時候執行,即最后執行
                .then(Mono.fromRunnable(() ->
                        log.info("[ {} ] 接口的訪問耗時:{} /ms", 
                        path, System.currentTimeMillis() - start))
                );
    }
}

最后需要使該全局過濾器生效,方法有很多種,可以直接在該類上加@Component注解,也可以通過代碼配置(@Bean),還有其他的一些方式。這里個人比較傾向于使用一個專門的配置類去實例化這些全局過濾器并交給Spring容器管理。代碼如下:

package com.zj.node.gateway.config;

import com.zj.node.gateway.filter.MyGlobalFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Slf4j
@Configuration
public class FilterConfig {

    @Bean
    // 該注解用于指定過濾器的執行順序,數字越小越優先執行
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter myGlobalFilter(){
        log.info("create myGlobalFilter...");
        return new MyGlobalFilter();
    }
}

啟動項目,看看我們自定義的全局過濾器是否已生效,訪問Gateway控制臺輸出如下:
第二代微服務網關組件 - Spring Cloud Gateway

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

夹江县| 肥乡县| 大余县| 苏尼特左旗| 天镇县| 新乐市| 翁牛特旗| 高邮市| 灯塔市| 澳门| 昌宁县| 喜德县| 四川省| 曲沃县| 兴安县| 广灵县| 金山区| 盐源县| 吴堡县| 永定县| 浏阳市| 洛隆县| 资讯| 称多县| 岫岩| 杨浦区| 翁牛特旗| 高邮市| 阿拉善左旗| 咸丰县| 偃师市| 武鸣县| 宣城市| 建德市| 阿克苏市| 祁连县| 北碚区| 诸暨市| 柳林县| 怀化市| 历史|