Spring|Spring Cloud Gateway实战之二(更多路由配置方式)

欢迎访问我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
本篇概览

  • 本文是《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|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

准备工作
  • 正式开始前需要再做一点准备工作,整个《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,确认已经注册:
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

  • 准备完毕,可以开始实战了
目标地址支持用服务名(取代之前的IP+端口)
  • 咱们从最简单的开始,先看前文的路由配置,如下图红框,目标地址是IP+端口:
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

  • 玩过Spring Cloud的您自然看出了问题所在:没有注册发现,确实,这样将地址和端口写死在配置文件中是不合适的,咱们先来解决这个问题;
  • 新增名为gateway-by-loadbalance的子工程,其pom.xml中的依赖情况如下,可见重点是spring-cloud-starter-loadbalancer
com.bolingcavalry common ${project.version} org.springframework.cloud spring-cloud-starter-gateway io.projectreactor reactor-test test org.springframework.boot spring-boot-starter-test test org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery

  • 启动类的代码省去了(和前文的一样)
  • 配置信息如下,重点是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:准确找到服务:
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

支持在nacos上配置
  • 将所有配置信息写在application.yml中有个问题:不能远程配置,这在应用数量较多的场景就不方便了,好在nacos提供了远程配置的能力,应用启动后可以从nacos取得自己的配置信息,咱们来试试
  • 新增名为gateway-nacos-config的子工程,其pom.xml中的依赖情况如下,请注意里面的中文注释,每指明了每一个依赖的作用:
com.bolingcavalry common ${project.version} org.springframework.cloud spring-cloud-starter-gateway io.projectreactor reactor-test test org.springframework.boot spring-boot-starter-test test org.springframework.cloud spring-cloud-starter-bootstrap org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery

  • 本地的配置文件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增加一个配置文件,操作如下图红框:
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

  • 增加一个配置,要注意的地方如下(配置信息的文本稍后给出,便于复制):
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

  • 上图中完整的配置信息如下:
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获取配置文件成功:
Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

写代码的方式配置
  • 前面的几个例子,路由信息都是写在配置文件中的,其实还有一种方式:写代码配置路由,能自己写代码来配置,这灵活性就更强了
  • 新增名为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
  • 执行单元测试可以顺利通过,证明代码配置路由没有问题:
【Spring|Spring Cloud Gateway实战之二(更多路由配置方式)】Spring|Spring Cloud Gateway实战之二(更多路由配置方式)
文章图片

  • 至此,负载均衡、nacos配置、代码配置的实例咱们都尝试过了,它们合起来会给实际生存环境的配置带来很大的方便,希望能够给您一些参考
缺陷和解决之道
  • 上述配置方式虽多,但有一个共同的问题:每当配置变动后,Gateway应用需要重启才能生效,这在请求不间断的生产环境是难以接受的
  • 为了让最新的路由配置能在Gateway应用不重启的前提下生效,接下来的文章咱们一起去探索动态路由是如何实现的
你不孤单,欣宸原创一路相伴
  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列
欢迎关注公众号:程序员欣宸
微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...
https://github.com/zq2599/blog_demos

    推荐阅读