7. 网关我选 Spring Cloud Gateway

作者: 风筝 / 2023-06-02

gateway, Java, JDK, Spring Cloud, Spring Cloud Gateway, 网关

网关可提供请求路由与组合协议转换安全认证服务鉴权流量控制日志监控等服务。可选的网关有不少,比如 Nginx、高性能网关 OpenResty、Linkerd 以及 Spring Cloud Gateway。

如果是真的追求高性能,那肯定是选择 Nginx 或者 OpenResty 无疑了, 但是对性能要求不是很高的话,并且又在用 Spring Cloud 系列,那当然就要选择 Spring Cloud Gateway 了。

网关的基础就是路由功能,通俗解释就是地址转发,将一个请求地址转发到实际的服务地址。比如请求的是 http://xxx.com/api 的路由地址,实际上会被转发到 http://xxx.com:8888 上来,这就是个最简单的路由方式。

我们可以理解为 Spring Cloud Gateway 就是针对进来的请求做各种判断和处理,比如说判断请求的合法性、权限验证,请求地址改写,请求参数、头信息、cookie 信息的分析和改写,请求速率控制,日志留存等。而这些都可以方便的通过 Predicate 和 GatewayFilter 来组合实现。

创建 Spring Cloud Gateway 项目

Spring Cloud 版本是 Greenwich.SR2,Spring Boot 版本 2.1.6.RELEASE,JDK 1.8。

接下来正式创建一个 Gateway 项目。

首先做两个微服务,当做路由转发的目标服务

两个微服务是以 consul 作为服务注册中心的,可以看这篇文章服务注册发现、配置中心集一体的 Spring Cloud Consul

1、创建 consul-order 服务,具体可以去 github 上看代码,很简单的一个服务。创建的 RESTful Controller 如下:

@RestController
@RequestMapping(value = "order")
public class OrderController {

    @Value("${spring.application.name}")
    private String applicationName;


    @GetMapping(value = "get")
    public CustomerOrder getOrder(){
        CustomerOrder customerOrder = new CustomerOrder();
        customerOrder.setOrderId("9999");
        customerOrder.setProductName("MacBook Pro");
        customerOrder.setClient(applicationName);
        return customerOrder;
    }
}

总之,最后直接访问这个接口的地址为 http://localhost:5006/order/get

2、创建 consul-user 服务,具体代码可以到 github 上查看

创建的 RESTful Controller 内容如下:

@RestController
@RequestMapping(value = "user")
public class UserController {

    @GetMapping(value = "get")
    public User getUserInfo(){
        User user = new User();
        user.setName("古时的风筝");
        user.setAge(8);
        user.setLocation("北京");
        return user;
    }
}

和上面的微服务有点区别的就是设置了 context-path

server:
  port: 5005
  servlet:
    context-path: /user-service

之所以这样不同的设置,是因为下面要验证一个 filter。总之,最后上述接口的访问地址为:http://localhost:5005/user-service/user/get

创建一个项目,并引入 maven 包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置简单的路由转发

路由配置有两种方式。一种是配置文件,另外一种是代码方式配置,WebFlux 的反应式编程方式。所以我们 pom 文件中要引入 WebFlux 的包。这是 Spring 5 的新特性。

1、先看第一种配置文件方式配置:

server:
  port: 10000
spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: false # 是否将服务id转换为小写
      routes:
      - id: userServiceRouter
        uri: lb://consul-user
        predicates:
        - Path=/user-service/**
      - id: orderServiceRouter
        uri: lb://consul-order
        predicates:
        - Path=/order-service/**
        filters:
        - StripPrefix=1
    consul:
      host: localhost #注册gateway网关到consul
      port: 8500
      discovery:
        service-name: service-gateway

其中包括 Spring Boot 项目的基本配置,name、port ,还有关于 consul 的配置,要将网关服务注册到注册中心。

上面配置中创建了两条路由规则,路由规则名称通过 id 设置,分别是 userServiceRouter 和 orderServiceRouter,通过 predicates.Path 设置待转发的 url,通过 uri 设置转发后的目标地址。上面配置将以 /user-service/开头的地址转发到 lb://consul-user ,固定格式 lb + 服务id,在有注册中心的情况下要这样写,如过没有注册中心,可以直接写目标 url。

下面的路由规则中多了一个 StripPrefix 的 filter ,这个是 Gateway 的内置 filter,作用就是去掉 Path 中的指定部分,StripPrefix=1,就是以 / 分隔,去掉第一部分,比如 /a/b/c 这个地址,在 StripPrefix=1 的作用下,就会转发到 /b/c/,当 StripPrefix=2 的时候,就会转发到 /c/。

配置好上述接口,然后启动网关服务。访问规则就会有如下对应关系:

http://localhost:10000/user-service/user/get->http://localhost:5005/user-service/user/get

http://localhost:10000/order-service/order/get->http://localhost:5006/order/get

当然这只是针对每一个目标服务只有一个实例的情况,如果有多个实例,就会按照负载策略落到对应的实例中。

2、代码方式的路由配置

@Bean
public RouteLocator kiteRouteLocator(RouteLocatorBuilder builder) {
	return builder.routes()
		.route("userRouter", r -> r.path("/user-service/**")
			.filters(f ->
				f.addResponseHeader("X-CustomerHeader", "kite"))
			.uri("lb://consul-user")
		)
		.route("orderRouter", r -> r.path("/order-service/**")
			.filters(f -> f.stripPrefix(1)).uri("lb://consul-order")
		)
		.build();
}

上面的这段代码和前面的配置文件的内容是同样的作用。只要实现一个返回类型为 RouteLocator,参数为 RouteLocatorBuilder类型的 Bean。

你看后面那一连串的 r.path().filters().uri() 了吗,用它们就可以简单的配置出路由规则,而且可读性也比较强。另外,Gateway 还套用了 Predicate 的规则来构建更加灵活、复杂的路由规则。Predicate 是 Java 8 增加的逻辑计算库,有 negate()、and()、or()、isEqual()几个方法。具体的代码在 PredicateSpec 和 UriSpec 这两个类里,一目了然。

PredicateSpec 里有这么多方法,都可以结合 and、or 组合起来使用。

接下来就说到 filter,Gateway 内置了很多的 filter,可以在 GatewayFilterSpec 类下找到方法封装,每一个 filter 都由一个 factory 的 apply 实现,都在 org.springframework.cloud.gateway.filter.factory包下,有必要的话可以直接看源码。 比如上面用到的 StripPrefix。还有 addResponseHeader,它的作用是在 Response 对象的 header 中添加请求头。

启动网关服务

启动网关,并访问两个接口测试,接口分别为 http://localhost:10000/user-service/user/get和http://localhost:10000/order-service/order/get,正常返回数据,则说明网关服务配置正常。

巧用 StripPrefix filter

微服务多了之后,路由的转发规则也就多了,比方说订单相关请求要转发到订单微服务集群,用户相关请求要转发到用户微服务集群,最终开放给终端的接口也要能表明是哪个微服务的,除了接口文档里说明之外,接口本身最好也能明确标识。

一种方式是在微服务的配置文件中配置上server.servlet.context-path

还有一种方式就是在路由规则的 path 中配置,然后加上 StripPrefix 配置,选择性的去掉请求 url 中的某些部分。比如我们请求 Gateway的地址为 order-service/order/get,则经过 StripPrefix(1) 之后,会把请求地址变为 order/get,然后根据路由规则定向到具体的微服务地址或者特定的 url。

本篇就介绍 Spring Cloud Gateway 的基本用法,后续还会有关于集成安全认证、鉴权、限流、日志等相关内容,敬请关注。

相关文章

0. Spring Cloud 是什么
1. Spring Cloud Eureka 实现服务注册与发现
2. Spring Cloud Eureka 实现安全控制
3. Spring Cloud Eureka 实现高可用服务发现注册中心
4. Spring Cloud Config 实现配置中心,看这一篇就够了
风筝

作者

风筝

古时的风筝,一个平庸的程序员,主语言 Java,第二语言 Python,其实学 Python 的时间比 Java 还要早。喜欢写博客,写博客的过程能加深自己对一个知识点的理解,同时还可以分享给他人。喜欢做一些小东西,所以也会一些前端的东西,React、JavaScript、CSS 都会一些,做一些小工具还够用。