Spring Cloud Gateway實戰之二:更多路由配置方式

程式設計師欣宸發表於2021-11-09

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概覽

  • 本文是《Spring Cloud Gateway實戰》系列的第二篇,通過前文我們們瞭解到Spring Cloud Gateway的核心是路由配置,然後在本地application.yml中配置了一條路由,但這種修改本地配置檔案的方式缺乏靈活性,未必能滿足靈活多變的業務需求,因此,本篇的目的就是找出本地配置之外的其他配置方式來,滿足各種實際需求;
  • 總的來說以下三種方式都是常用的:
  1. 目標地址支援用服務名(取代之前的IP+埠);
  2. 支援在nacos上配置;
  3. 支援寫程式碼的方式配置;
  • 另外還有一種更加靈活的配置方式:動態代理,因為涉及到不少的程式碼所以會單獨出一篇文章詳細介紹

原始碼下載

名稱 連結 備註
專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該專案原始碼的倉庫地址,https協議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協議
  • 這個git專案中有多個資料夾,本篇的原始碼在spring-cloud-tutorials資料夾下,如下圖紅框所示:

在這裡插入圖片描述

準備工作

  • 正式開始前需要再做一點準備工作,整個《Spring Cloud Gateway實戰》系列中,所有請求最後都會被路由到provider-hello這個web上去,該服務目前只有一個web介面/hello/str,現在我們們再給它增加一個,後面的實戰會用到

  • 新增加的web介面來自LBTest.java,可見非常簡單:

package com.bolingcavalry.provider.controller;

import com.bolingcavalry.common.Constants;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
@RequestMapping("/lbtest")
public class LBTest {

    private String dateStr(){
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
    }

    /**
     * 返回字串型別
     * @return
     */
    @GetMapping("/str")
    public String helloStr() {
        return Constants.LB_PREFIX + ", " + dateStr();
    }
}
  • 上述程式碼中的Constants.LB_PREFIX來自子工程common
package com.bolingcavalry.common;

public interface Constants {
    String HELLO_PREFIX = "Hello World";
    String LB_PREFIX = "Load balance";
}
  • 寫完程式碼後,先確保nacos已經啟動

  • 在啟動provider-hello工程,啟動成功後去看nacos,確認已經註冊:

在這裡插入圖片描述

  • 準備完畢,可以開始實戰了

目標地址支援用服務名(取代之前的IP+埠)

  • 我們們從最簡單的開始,先看前文的路由配置,如下圖紅框,目標地址是IP+埠:

在這裡插入圖片描述

  • 玩過Spring Cloud的您自然看出了問題所在:沒有註冊發現,確實,這樣將地址和埠寫死在配置檔案中是不合適的,我們們先來解決這個問題;

  • 新增名為gateway-by-loadbalance的子工程,其pom.xml中的依賴情況如下,可見重點是spring-cloud-starter-loadbalancer

<dependencies>
        <dependency>
            <groupId>com.bolingcavalry</groupId>
            <artifactId>common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 路由策略使用lb的方式是,這個依賴一定要有 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!--nacos:註冊中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
  • 啟動類的程式碼省去了(和前文的一樣)

  • 配置資訊如下,重點是uri的值lb://provider-hello,用了字首lb:,後面的provider-hello就是在nacos註冊的服務名:

server:
  #服務埠
  port: 8085
spring:
  application:
    name: gateway-by-loadbalance
  cloud:
    nacos:
      # 註冊中心的配置
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: path_route_lb
          uri: lb://provider-hello
          predicates:
          - Path=/lbtest/**
  • 單元測試類:
package com.bolingcavalry.gateway;

import com.bolingcavalry.common.Constants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
public class HelloTest {

    @Autowired
    private WebTestClient webClient;

    @Test
    void testLoadBalance() {
        webClient.get()
                .uri("/lbtest/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 驗證狀態
                .expectStatus().isOk()
                // 驗證結果,注意結果是字串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.LB_PREFIX)));
    }
}
  • 執行單元測試,通過,可見上述配置可以通過字首lb:準確找到服務:

在這裡插入圖片描述

支援在nacos上配置

  • 將所有配置資訊寫在application.yml中有個問題:不能遠端配置,這在應用數量較多的場景就不方便了,好在nacos提供了遠端配置的能力,應用啟動後可以從nacos取得自己的配置資訊,我們們來試試

  • 新增名為gateway-nacos-config的子工程,其pom.xml中的依賴情況如下,請注意裡面的中文註釋,每指明瞭每一個依賴的作用:

<dependencies>
        <dependency>
            <groupId>com.bolingcavalry</groupId>
            <artifactId>common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 使用bootstrap.yml的時候,這個依賴一定要有 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- 路由策略使用lb的方式是,這個依賴一定要有 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!--nacos:配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos:註冊中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
  • 本地的配置檔案bootstrap.yml,非常簡單,就是nacos的地址和遠端配置資訊:
spring:
  application:
    name: gateway-nacos-config
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        group: DEFAULT_GROUP
  • 接下來再nacos增加一個配置檔案,操作如下圖紅框:

在這裡插入圖片描述

  • 增加一個配置,要注意的地方如下(配置資訊的文字稍後給出,便於複製):

在這裡插入圖片描述

  • 上圖中完整的配置資訊如下:
server:
  port: 8083
spring:
  cloud:
    gateway:
      routes:
        - id: path_route_addr
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
        - id: path_route_lb
          uri: lb://provider-hello
          predicates:
          - Path=/lbtest/**
  • 測試類中的兩個測試方法如下所示,和前面沒有任何區別:
@Test
    void testHelloPredicates() {
        webClient.get()
                .uri("/hello/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 驗證狀態
                .expectStatus().isOk()
                // 驗證結果,注意結果是字串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.HELLO_PREFIX)));
    }

    @Test
    void testLoadBalance() {
        webClient.get()
                .uri("/lbtest/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 驗證狀態
                .expectStatus().isOk()
                // 驗證結果,注意結果是字串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.LB_PREFIX)));
    }
  • 執行單元測試類,測試通過,證明從nacos獲取配置檔案成功:

在這裡插入圖片描述

寫程式碼的方式配置

  • 前面的幾個例子,路由資訊都是寫在配置檔案中的,其實還有一種方式:寫程式碼配置路由,能自己寫程式碼來配置,這靈活性就更強了

  • 新增名為gateway-by-code的子工程,其pom.xml檔案參照前面工程的即可

  • 接下來的本例的重點,在配置類中增加一個RouteLocator型別的bean,通過以下程式碼即可增加一個路由:

package com.bolingcavalry.gateway.cofig;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator customizeRoute(RouteLocatorBuilder builder) {
        return builder
                .routes()
                .route(
                            // 第一個引數是路由的唯一身份
                        "path_route_lb",
                            // 第二個引數是個lambda實現,
                            // 設定了配套條件是按照請求路徑匹配,以及轉發地址,
                            // 注意lb://表示這是個服務名,要從
                            r -> r.path("/lbtest/**").uri("lb://provider-hello")
                )
                .build();
    }
}
  • 上述程式碼只配置了一個路由,還有一個在配置檔案中,這樣就能驗證程式碼和配置檔案能不能同時生效了:
server:
  #服務埠
  port: 8084
spring:
  application:
    name: gateway-by-code
  cloud:
    nacos:
      discovery:
        # nacos服務地址
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: path_route_addr
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
  • 測試類和之前工程的一模一樣,就不佔用篇幅了,依舊是兩個測試方法testHelloPredicatestestLoadBalance

  • 執行單元測試可以順利通過,證明程式碼配置路由沒有問題:

在這裡插入圖片描述

  • 至此,負載均衡、nacos配置、程式碼配置的例項我們們都嘗試過了,它們合起來會給實際生存環境的配置帶來很大的方便,希望能夠給您一些參考

缺陷和解決之道

  • 上述配置方式雖多,但有一個共同的問題:每當配置變動後,Gateway應用需要重啟才能生效,這在請求不間斷的生產環境是難以接受的
  • 為了讓最新的路由配置能在Gateway應用不重啟的前提下生效,接下來的文章我們們一起去探索動態路由是如何實現的

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中介軟體系列
  6. DevOps系列

歡迎關注公眾號:程式設計師欣宸

微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos

相關文章