小白学习spring-cloud(五): spring-cloud-gateway的路由配置方式

155 阅读5分钟

前言

通过前文咱们了解到Spring Cloud Gateway的核心是路由配置,然后在本地application.yml中配置了一条路由,但这种修改本地配置文件的方式缺乏灵活性,未必能满足灵活多变的业务需求,因此,本篇的目的就是找出本地配置之外的其他配置方式来,满足各种实际需求。

总的来说以下三种方式都是常用的:

  1. 目标地址支持用服务名(取代之前的IP+端口);
  2. 支持在nacos上配置;
  3. 支持写代码的方式配置;
  4. 动态代理,因为涉及到不少的代码所以会单独出一篇文章详细介绍

配置方式介绍

一、目标地址支持用服务名(取代之前的IP+端口)

先看之前的路由配置,如下图红框,目标地址是IP+端口:

image.png

没有注册发现,这样将地址和端口写死在配置文件中是不合适的,咱们先来解决这个问题。

  • 新增名为gateway-by-loadbalance的子模块,其pom.xml中的依赖情况如下,可见重点是spring-cloud-starter-loadbalancer
<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>
  • application.yml的配置信息如下,重点是uri的值lb://nacos-provider,用了前缀lb:,后面的nacos-provider就是在nacos注册的服务名:
server:
  port: 10009

spring:
  application:
    name: gateway-by-loadbalance
  cloud:
    nacos:
      # 注册中心的配置
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: path_route_lb
          uri: lb://nacos-provider
          predicates:
            - Path=/nacos/**
  • 单元测试类:
package com.gateway;

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("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }
}

二、支持在nacos上配置

  • 将所有配置信息写在application.yml中有个问题:不能远程配置,这在应用数量较多的场景就不方便了,好在nacos提供了远程配置的能力,应用启动后可以从nacos取得自己的配置信息,咱们来试试

  • 新增名为gateway-nacos-config的子模块,其pom.xml中的依赖情况如下,请注意里面的中文注释,指明了每一个依赖的作用:

<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>
  • 本地的配置文件bootstrap.yml,非常简单,就是nacos的地址和远程配置信息:
server:
  port: 100010

spring:
  application:
    name: gateway-nacos-config
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: DEFAULT_GROUP
  • 接下来再nacos增加一个配置文件,操作如下图红框:

image.png

  • 增加一个配置,要注意的地方如下(配置信息的文本稍后给出,便于复制):

image.png

  • 上图中完整的配置信息如下:
server:
   port: 100010

spring:
   cloud:
     gateway:
        routes:
          - id: path_route_addr
            uri: http://localhost:9001
            predicates:
                - Path=/nacos/**
          - id: path_route_lb
            uri: lb://nacos-consumer
            predicates:
               - Path=/consumer/**
  • 测试类:
package com.gateway;

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("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }

    @Test
    void testConsumer() {
        webClient.get()
                .uri("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }
}

三、写代码的方式配置

  • 新增名为gateway-by-code的模块,其pom.xml文件内容如下:
<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>
  • 接下来的本例的重点,在配置类中增加一个RouteLocator类型的bean,通过以下代码即可增加一个路由:
package com.gateway.config;

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("/nacos/**").uri("lb://nacos-provider")
                )
                .build();
    }
}
  • 测试类
package com.gateway;

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("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }

    @Test
    void testConsumer() {
        webClient.get()
                .uri("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }
}

缺陷和解决之道

  • 上述配置方式虽多,但有一个共同的问题:每当配置变动后,Gateway应用需要重启才能生效,这在请求不间断的生产环境是难以接受的
  • 为了让最新的路由配置能在Gateway应用不重启的前提下生效,接下来的文章咱们一起去探索动态路由是如何实现的