微服務學習之Gateway服務網關【Hoxton.SR1版】

目錄

1 Gateway是什麼

2 能幹嘛

3 Gateway特性

4 Gateway與Zuul的區別

5 Gateway三大核心概念

5.1 路由Route

5.2 斷言Predicate

5.3 過濾器(Filter)

5.4 Gateway工作流程

6 Gateway代碼實操

6.1 基本環境搭建

6.1.1 pom依賴

6.1.2 application.yml(yml配置方式配置路由)

6.1.3 主啓動類

6.1.4 服務提供者

6.2 Gateway測試

6.2.1 yml方式配置路由測試

6.2.2 代碼中注入RouteLocator的Bean的方式配置路由測試

6.2.3 動態路由配置

6.2.4 斷言Predicate

6.2.5 Route Filter路由過濾器

7 總結


1 Gateway是什麼

Gateway是一個在Spring生態系統之上構建的API網關,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一種簡單而有效的方法來路由到API,併爲它們提供跨領域的關注點,例如:安全性,監視/指標和彈性。(官網介紹

SpringCloud全家桶中有一個很重要的組件就是網關,在1.x版本中都是採用的Zuul網關,但在2.x版本中,Netflix對Zuul的升級工作一直跳票,最終SpringCloud自己研發了Gateway用來替代Zuul。Netflix很多組件(Eureka、Hystrix等)都進入維護階段,選擇Gateway做網關是更有保障的,畢竟是SpringCloud團隊自己開發的。

SpringCloud Gateway作爲SpringCloud生態系統中的網關,目標是替代Zuul。在SpringCloud2.0以上版本中,沒有對新版本的Zuul2.0最新高性能版本進行集成,仍然還是使用zuul1.x非Reactor模式的老版本。爲了提升網關的性能,SpringCloud Gateway是基於WebFlex框架實現的,而WebFlex框架底層使用了Reactor模式通信框架Netty。

官網Gateway的工作原理圖:

Spring Cloudç½å³å¾

2 能幹嘛

反向代理、鑑權、流量控制、熔斷、日誌監控等等。

微服務中網關處於什麼位置?

3 Gateway特性

1 ),基於Spring 5,Spring Boot 2和Project Reactor進行構建。

2),動態路由能夠匹配任何請求屬性。

3),可以對路由指定Predicate(斷言)和Filter(過濾),易於編寫Predicate和Filter。

4),集成Hystrix的斷路器功能。

5),集成SpringCloud的服務發現功能。

6),請求限流功能。

7),支持路徑重寫。

4 Gateway與Zuul的區別

5 Gateway三大核心概念

5.1 路由Route

路由是構建網關的基本模塊,它由ID、目標URL、一系列的斷言、過濾器組成,如果斷言爲true,那麼匹配該路由。

5.2 斷言Predicate

參考Java 8 Function Predicate,開發人員可以匹配Http請求中的所有內容(例如請求頭、請求參數),如果請求與斷言相匹配則進行路由。

5.3 過濾器(Filter)

過濾器是Spring框架中GatewayFilter的實例,使用過濾器可以實現在請求被路由前或者之後對請求進行修改。

5.4 Gateway工作流程

路由轉發+執行過濾器鏈。

6 Gateway代碼實操

Gateway網關路由有兩種配置方式:yml文件配置、代碼中注入RouteLocator的Bean。

6.1 基本環境搭建

6.1.1 pom依賴

在父工程下新建一個名爲cloud-gateway-gateway9527的module,對應的pom依賴如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.bighuan.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入自定義的api通用包,可以使用Payment支付Entity-->
        <dependency>
            <groupId>com.bighuan.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.bighuan.springcloud.GatewayMain9527</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

6.1.2 application.yml(yml配置方式配置路由)

application.yml文件配置如下。關於路由配置可以參考官網:id表示路由的ID,沒有固定的規則,但是一定要唯一,可以配合服務名來配置;uri表示匹配後提供服務的路由地址;predicates下的Path是爲了匹配對應的路徑。

關於註冊中心的相關配置,都在此前的博客中有相關記錄,不再贅述。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh          # payment_route 路由的ID,沒有固定規則但要求唯一,建議配合服務名
          uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
          predicates:
            - Path=/payment/get/**    # 斷言,路徑相匹配的進行斷言
        - id: payment_routh2          # 路由的ID,沒有固定規則但要求唯一,建議配合服務名
          uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
          predicates:
            - Path=/payment/lb/**    # 斷言,路徑相匹配的進行斷言
eureka:
  instance:
    hostname: cloud-gateway-service
  client: # 服務提供者的provider註冊進eureka的服務列表裏
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

6.1.3 主啓動類

主啓動類並沒有特殊的配置,比較簡單。

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class,args);
    }
}

6.1.4 服務提供者

在yml文件中關於路由的配置那一塊,uri和predicates下的Path等配置都是爲了指向服務提供者。服務提供者相關的博客可參考此前的博客,爲行文方便,只粘貼出對應的controller。

 @GetMapping(value = "/payment/lb")
    public String getPaymentLB(){
        return serverPort;
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {

        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查詢結果*****:" + payment+",server.port:"+serverPort);
        if (payment != null) {
            return new CommonResult(200, "查詢成功,serverPort:"+serverPort, payment);
        } else {
            return new CommonResult(444, "沒有對應記錄,查詢ID:"+id+","+"serverPort:"+serverPort, null);
        }
    }

6.2 Gateway測試

6.2.1 yml方式配置路由測試

分別啓動註冊中心Eureka7001、服務提供者8001、Gateway9527,瀏覽器分別訪問:

http://127.0.0.1:9527/payment/get/31http://127.0.0.1:9527/payment/lb

發現都成功返回了對應的數據,前者需要查詢數據庫,後者則直接返回服務提供者的端口8001。通過Gateway9527,將服務提供者隱藏起來,對外界是不可見的,通過服務網關的路由就可以訪問。

6.2.2 代碼中注入RouteLocator的Bean的方式配置路由測試

代碼配置方式可參考官網的配置,如下:

本文配置類代碼如下,特別注意的是,需要讓此類可以被主啓動類掃描到。

//代碼配置: 配置路由的第二種方式
@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();

        return routes.route("path_route_bighuan",
                r -> r.path("/guonei").uri("http://news.baidu.com/guonei"))
                .build();
    }
}

同樣,分別啓動註冊中心7001、服務提供者8001(只測試代碼配置路由,不需要啓動也可以)、Gateway9527,訪問http://127.0.0.1:9527/guonei,頁面跳轉到到了國內百度新聞的頁面。

6.2.3 動態路由配置

以上的測試中,只有一個服務提供者8001,如果不止一個服務提供者8001,還有服務提供者8002、8003,每個都要在配置文件中配置一次的話,那麼是不現實的。動態路由就可以解決這個問題。

默認情況下,Gateway會根據註冊中心註冊的服務列表,以註冊中心上微服務名爲路徑創建動態路由進行轉發,從而實現動態路由的功能。

動態路由的配置文件爲,application-dynamic.yml,配置如下:

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 開啓從註冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: payment_routh          # payment_route 路由的ID,沒有固定規則但要求唯一,建議配合服務名
          # uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
          uri: lb://CLOUD-PAYMENT-SERVICE # 匹配後提供服務的路由地址
          predicates:
            - Path=/payment/get/**    # 斷言,路徑相匹配的進行斷言
        - id: payment_routh2          # 路由的ID,沒有固定規則但要求唯一,建議配合服務名
          # uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
          uri: lb://CLOUD-PAYMENT-SERVICE # 匹配後提供服務的路由地址
          predicates:
            - Path=/payment/lb/**   # 斷言,路徑相匹配的進行斷言
eureka:
  instance:
    hostname: cloud-gateway-service
  client: # 服務提供者的provider註冊進eureka的服務列表裏
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

說明:

1)spring.cloud.gateway.discovery.locator.enabled設置爲true,開啓從註冊中心動態創建路由的功能,利用微服務名進行路由。

2)uri的協議爲lb,表示基於服務註冊的負載均衡,即啓動Gateway的負載均衡功能,CLOUD-PAYMENT-SERVICE是服務提供者的微服務名。

測試步驟:啓動一個註冊中心Eureka7001、分別啓動服務提供者8001和8002、啓動Gateway9527,然後連續訪問:http://127.0.0.1:9527/payment/lb,返回結果則爲8001、8002交替出現,說明動態路由配置成功,而且負載均衡也實現了。

6.2.4 斷言Predicate

SpringCloud Gateway包含許多內置的Route Predicate工廠,所有這些Predicate都與HTTP請求的不同屬性相匹配,多個Predicate工廠可以進行組合。可以理解爲Predicate就是 Sql語句中Where後的條件,可以有多個條件進行條件限制。

SpringCloud Gateway創建Route對象時,使用RoutePredicateFactory創建Predicate對象,Predicate對象可以賦值給Route對象。官網可以看到,至少內置了11中Route Predicate Factory。其中,第8種Path Route Predicate Factory前文已經接觸過了,主要用來匹配路徑。

 

在application-dynamic.yml的payment_routh2的predicates下增加after配置,表示需要在該時間之後才能成功匹配路由匹配。

- After=2020-03-28T17:25:10.065+08:00[Asia/Shanghai]

這種特殊的時間可以通過ZoneDateTime來獲取

        ZonedDateTime now = ZonedDateTime.now();
        // 2020-03-28T16:51:32.977+08:00[Asia/Shanghai]
        System.out.println(now);

分別啓動項目,在設置的after時間之前訪問,會報錯。

當時間過了after時間之後,就可以正常訪問了。

Before、Between Route Predicate Factory的配置方式類似。

還是在application-dynamic.yml的payment_routh2的predicates下增加cookie配置。

- Cookie=username,bighuan

分別啓動項目,一開始請求時不帶cookie,直接返回404;帶上cookie後,訪問正常。

6.2.5 Route Filter路由過濾器

路由過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器適用於特定路由。Spring Cloud Gateway包括許多內置的GatewayFilter工廠。Spring Cloud Gateway內置了多種路由過濾器,它們都GatewayFilter工廠類產生。(官網gatewayfilter-factories,配置參考官網即可)

Filter的生命週期有兩種:pre、post。

在“pre”類型的過濾器可以做參數校驗、權限校驗、流量監控、日誌輸出、協議轉換等,在“post”類型的過濾器中可以做響應內容、響應頭的修改,日誌輸出,流量監控等。(注:這段話來自某大佬博客)

Filter的種類有兩種:GatewayFilter、GlobalFilter。

自定義全局GlobalFilter過濾器

自定義全局過濾器,需要實現GlobalFilter、Ordered兩個接口。自定義全局過濾器,可以實現全局日誌記錄、統一網關鑑權等等。

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("******come in MyLogGateWayFilter:"+new Date());
        String uname=exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname == null){
            log.info("*****用戶名爲空,是非法用戶,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 加載過濾器的優先級,值越小,優先級越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

測試:使用application.yml啓動Gateway9527,訪問http://127.0.0.1:9527/payment/lb?uname=bighuan可以通過獲得結果,http://127.0.0.1:9527/payment/lb則會被攔截。

7 總結

雖然寫這篇博客花的的時間比較長,但堅持堅持,會有收穫的!

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章