Spring Cloud Gateway实战之二:更多路由配置方式

2021-12-07 09:05:50 浏览数 (1)

本篇概览

  • 本文是《Spring Cloud Gateway实战》系列的第二篇,通过前文咱们了解到Spring Cloud Gateway的核心是路由配置,然后在本地application.yml中配置了一条路由,但这种修改本地配置文件的方式缺乏灵活性,未必能满足灵活多变的业务需求,因此,本篇的目的就是找出本地配置之外的其他配置方式来,满足各种实际需求;
  • 总的来说以下三种方式都是常用的:
  1. 目标地址支持用服务名(取代之前的IP 端口);
  2. 支持在nacos上配置;
  3. 支持写代码的方式配置;
  • 另外还有一种更加灵活的配置方式:动态代理,因为涉及到不少的代码所以会单独出一篇文章详细介绍

源码下载

  • 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):

名称

链接

备注

项目主页

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,可见非常简单:
代码语言:javascript复制
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:
代码语言:javascript复制
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
代码语言:javascript复制
<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注册的服务名:
代码语言:javascript复制
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/**
  • 单元测试类:
代码语言:javascript复制
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中的依赖情况如下,请注意里面的中文注释,每指明了每一个依赖的作用:
代码语言:javascript复制
<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的地址和远程配置信息:
代码语言:javascript复制
spring:
  application:
    name: gateway-nacos-config
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        group: DEFAULT_GROUP
  • 接下来再nacos增加一个配置文件,操作如下图红框:
  • 增加一个配置,要注意的地方如下(配置信息的文本稍后给出,便于复制):
  • 上图中完整的配置信息如下:
代码语言:javascript复制
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/**
  • 测试类中的两个测试方法如下所示,和前面没有任何区别:
代码语言:javascript复制
@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,通过以下代码即可增加一个路由:
代码语言:javascript复制
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();
    }
}
  • 上述代码只配置了一个路由,还有一个在配置文件中,这样就能验证代码和配置文件能不能同时生效了:
代码语言:javascript复制
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/**
  • 测试类和之前工程的一模一样,就不占用篇幅了,依旧是两个测试方法testHelloPredicates和testLoadBalance
  • 执行单元测试可以顺利通过,证明代码配置路由没有问题:
  • 至此,负载均衡、nacos配置、代码配置的实例咱们都尝试过了,它们合起来会给实际生存环境的配置带来很大的方便,希望能够给您一些参考

缺陷和解决之道

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

0 人点赞