Spring Cloud Gateway 入门实战

129 阅读8分钟

Spring Cloud Gateway 是 Spring Cloud 的子项目,提供了微服务网关功能,包括权限安全、过滤、限流、监控/指标等功能。

一、名词解释

1. Route

Route 即路由,Gateway 里面的 Route 是主要的学习内容,一个 Gateway 项目中可以包含多个 Route。

  • Route 包含了路由规则、校验、过滤、容错。其中路由规则可通过谓词来实现,校验、过滤和容错可通过过滤器来实现。
  • Route 由 ID、URL、Predicate集合、Filter集合构成。

2. Predicate

谓词,就是路由规则。比如 /api/a 路由到 A 服务,/api/b 路由到 B 服务上。

3. Filter

过滤器,Gateway中的 Filter 分为两类:Gateway Filter 和 Global Filter。在 Gateway 运行过程中 Filter 负载在代理服务之前、之后做一些额外的功能处理。

二、运行流程

spring_cloud_gateway_diagram.png

客户端向 Spring Cloud Gateway 发出请求。然后由 Gateway Handler Mapping 映射确定与请求相匹配的路由,将请求发送到网关 Web 处理程序 Gateway Web Handler。该程序通过指定的过滤器链将请求发送到后端服务对应的业务逻辑上,处理完成后返回。

1. 搭建一个Gateway 应用

  • 引入 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>web-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.10.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.10.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>

    <dependencies>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.webgateway.WebGatewayApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
  • 启动类
@SpringBootApplication
public class WebGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebGatewayApplication.class, args);
    }
}
  • 配置文件 application.properties
spring.application.name = web-gateway
server.port = 9940
logging.level.root = info

# 全局的TCP连接超时时间默认时间是45秒,所以也就是发生网络故障的时候,连接时间要等待45秒,而网络连接是同步阻塞的 ,The connect timeout in millis, the default is 45s. 所以就会导致请求非常慢,从网关就卡死了。
spring.cloud.gateway.httpclient.connect-timeout = 100000
#全局的响应超时时间,网络链接后,后端服务多久不返回网关就报错 The response timeout.
spring.cloud.gateway.httpclient.response-timeout = PT300S
#服务路由名小写
spring.cloud.gateway.discovery.locator.lower-case-service-id = true
# true开启通过服务中心的自动根据 serviceId 创建路由的功能
spring.cloud.gateway.discovery.locator.enabled = true

# 全局跨域
spring.cloud.gateway.globalcors.enable = true
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-origins = *
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allow-credentials = true
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods = *
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-headers = *
# 多个Access-Control-Allow-Origin跨域头只保留第一个(RETAIN_FIRST)
spring.cloud.gateway.default-filters[0] = DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

# 路由配置
spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=http://localhost:8081/
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**

三、路由规则

Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route. 路由断言工厂 RoutePredicateFactory 主要的实现类如下所示,包括 Datetime、请求的远端地址、路由权重、请求头、Host地址、请求方法、请求路径和请求参数的路由断言。

  1. DateTime

    • AfterRoutePredicateFactory:请求时间在指定时间点之后可用。
    • BeforeRoutePredicateFactory:请求时间在指定时间点之前可用。
    • BetweenRoutePredicateFactory:请求时间在指定时间段之间可用。

配置规则:

spring.cloud.gateway.routes[2].id=user-provider3
spring.cloud.gateway.routes[2].uri=http://localhost:8081/
# 匹配北京时间 2025-01-01 之后的请求
spring.cloud.gateway.routes[2].predicates[0]=After=2025-01-01T00:00:00+08:00

2. Cookie

*   **CookieRoutePredicateFactory**:请求 Cookie 正则匹配可用。

3. Header

*   **HeaderRoutePredicateFactory**:请求头属性正则匹配可用。
*   **CloudFoundryRouteServiceRoutePredicateFactory**:请求头包含指定属性可用。 <br/> 配置示例:

<!---->

    spring.cloud.gateway.routes[4].id=user-provider3
    spring.cloud.gateway.routes[4].uri=http://localhost:8081/
    # 匹配请求头包含 Request-Id 并且其值匹配正则表达式 \d+ 的请求
    spring.cloud.gateway.routes[4].predicates[0]=Header=Request-Id, \d+

4. Host

*   **HostRoutePredicateFactory**:请求 Host 匹配指定值。

5. Method

*   **MethodRoutePredicateFactory**:请求方法匹配指定值。 <br/> 配置示例:

<!---->

    spring.cloud.gateway.routes[1].id=user-provider2
    spring.cloud.gateway.routes[1].uri=http://localhost:8081/
    spring.cloud.gateway.routes[1].predicates[0]=Method=GET

6. Path

*   **PathRoutePredicateFactory**:请求路径正则匹配可用。 <br/>配置示例:
spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=http://localhost:8081/
# 可以给Path配置多个不同路径
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**,/order/**
  1. Query

    • QueryRoutePredicateFactory:请求参数正则匹配可用。
      配置示例:

spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=http://localhost:8081/
# 匹配请求参数中必须包含 token 并且 token 参数值是以 abc 开头的多个字符
spring.cloud.gateway.routes[0].predicates[0]=Query=token, ^abc.*

比如:

http://localhost:9940/user/get2?token=abc1
http://localhost:9940/user/get2?token=abc2lkj

8. RemoteAddr

*   **RemoteAddrRoutePredicateFactory**:请求远程地址匹配指定值可用。
spring.cloud.gateway.routes[3].id=user-provider3
spring.cloud.gateway.routes[3].uri=http://localhost:8081/
# 匹配远程服务器是:192.168.0.103 的地址,0 表示子网掩码
spring.cloud.gateway.routes[3].predicates[0]=RemoteAddr=192.168.0.103/0

9. Weight

*   **WeightRoutePredicateFactory**:根据路由组和指定权重进行匹配。

四、动态路由

动态路由其实是面向服务的路由,日常企业级开发通常使用这种配置方式来完成工作。 Spring Cloud Gateway 支持与 Eureka 整合开发,根据 serviceId 自动从注册中心中获取服务地址并转发请求,这样做在添加或者移除服务实例时不用修改 Gateway 的路由配置。

  • 添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 配置启动类
@EnableDiscoveryClient
@SpringBootApplication
public class WebGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebGatewayApplication.class, args);
    }
}
  • 配置文件
spring.application.name = web-gateway
server.port = 9940
logging.level.root = info

# 全局的TCP连接超时时间默认时间是45秒,所以也就是发生网络故障的时候,连接时间要等待45秒,而网络连接是同步阻塞的 ,The connect timeout in millis, the default is 45s. 所以就会导致请求非常慢,从网关就卡死了。
spring.cloud.gateway.httpclient.connect-timeout = 100000
#全局的响应超时时间,网络链接后,后端服务多久不返回网关就报错 The response timeout.
spring.cloud.gateway.httpclient.response-timeout = PT300S
#
spring.cloud.gateway.discovery.locator.lower-case-service-id = true
spring.cloud.gateway.discovery.locator.enabled = true

# 全局跨域
spring.cloud.gateway.globalcors.enable = true
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-origins = *
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allow-credentials = true
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods = *
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-headers = *
# 多个Access-Control-Allow-Origin跨域头只保留第一个(RETAIN_FIRST)
spring.cloud.gateway.default-filters[0] = DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

### 配置动态路由
spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=lb://user-provider
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**

### eureka 配置
eureka.client.service-url.defaultZone=http://admin:123@localhost:7901/eureka/,http://admin:123@localhost:7902/eureka/
# 客户端在注册中心中的名称
eureka.instance.instance-id=web-gateway
# 设置当前 client 每5秒向 server 发送一次心跳,默认 30s
eureka.instance.lease-renewal-interval-in-seconds=5
# 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒
eureka.instance.lease-expiration-duration-in-seconds=90
# 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到server
eureka.instance.prefer-ip-address=true
# 是否将自己注册到其他Eureka Server,默认为true
eureka.client.register-with-eureka=true
# 是否从eureka server获取注册信息, 需要
eureka.client.fetch-registry=true

1. 服务名称转发

通过一些两个配置实现服务名称转发,就是不配置具体的路由规则,Gateway 通过注册中心注册的实例,自动转发。

# 是否将服务名称转换成小写
spring.cloud.gateway.discovery.locator.lower-case-service-id = true
# 启用通过 serviceId 转发到具体服务示例
spring.cloud.gateway.discovery.locator.enabled = true

image.png

具体的请求方式:http://localhost:9940/user-provider/user/list

五、过滤器

Spring Cloud Gateway 根据作用范围将过滤器划分为:GatewayFilter 和 GlobalFilter,二者区别如下:

  • GatewayFilter:网关过滤器,需要通过 spring.cloud.routes.filters 配置在具体的路由下面,只作用于当前路由上。或者通过 spring.cloud.default-filters 配置在全局,作用在所有路由上。
  • GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它是请求业务以及路由的URL转换真实业务服务请求地址的核心过滤器,不需要配置,而是系统初始化时加载,并作用在所有的路由上。

1. 网关过滤器 GatewayFilter

aaaaa.png

Path 路径过滤器:

  • RewritePathGatewayFilterFactory,重写路径
spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=lb://user-provider
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**,/order/**,/api/**
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/api(?<segment>/?.*),${segment}

/api/** 这个请求路径后端服务是没有的,为了让他能够请求成功,RewritePath /api/user/**/api/order/** 重写为 /user/**/order/** ,重写之后的请求方式为:

http://localhost:9940/api/order/detail
  • PrefixPathGatewayFilterFactory

PrefixPath 网关过滤器工厂为匹配的 URL 添加指定的前缀

spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=lb://user-provider
spring.cloud.gateway.routes[0].predicates[0]=Path=/product/**
spring.cloud.gateway.routes[0].filters[0]=PrefixPath=/v1

后端服务对应的接口地址是:/v1/product/detail,通过网关配置以后可直接通过: http://localhost:9940/product/detail 对服务端发起请求。

  • StripPrefixGatewayFilterFactory

StripPrefix 网关过滤器工厂采用一个参数 StripPrefix,该参数表示在将请求发送到下游之前从请求中剔除路径个数。

spring.cloud.gateway.routes[0].id = user-provider
spring.cloud.gateway.routes[0].uri = lb://user-provider
spring.cloud.gateway.routes[0].predicates[0] = Path=/api/user/**
spring.cloud.gateway.routes[0].filters[0] = StripPrefix=1
  • SetPathGatewayFilterFactory

SetPath 网关过滤器工厂采用路径模板参数。它提供了一种模板化路径端来操作请求路径的简单方法,使用 Spring Framework 中的 URL 模板,允许多个匹配段。

spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=lb://user-provider
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/v1/product/{first}
spring.cloud.gateway.routes[0].filters[0]=SetPath=/v1/product/{first}

比如服务端接口地址:/v1/product/detail,使用 SetPath 网关过滤器工厂 就是给真实的请求地址加上虚拟路径。最终对外开放的地址:http://localhost:9940/api/v1/product/detail,其实可以理解为和 StripPrefix 网关过滤器工厂 的功能相似,但是没有 StripPrefix 灵活。

假如服务端有接口:/v1/product/list/p,此时配置应该改成:

spring.cloud.gateway.routes[0].id=user-provider
spring.cloud.gateway.routes[0].uri=lb://user-provider
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/v1/product/{first}/{p}
spring.cloud.gateway.routes[0].filters[0]=SetPath=/v1/product/{first}/{p}

2. 全局过滤器 GlobalFilter

全局过滤器不需要配置,自动作用在所有的路由上

Central_Topic.jpg

3. 自定义过滤器

3.1 自定义网关过滤器

public class CustomGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("自定义网关过滤器被执行");
        return chain.filter(exchange);
    }

    // 数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 注入配置
@Configuration
public class GatewayRoutesConfig {
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(
                        r -> r.path("/v1/product/**")
                                .uri("lb://user-provider")
                                // 注册自定义网关过滤器到指定路由上
                                .filters(new CustomGatewayFilter())
                                .id("user-provider")
                ).build();
    }
}

注入自定义过滤器配置以后,就不需要再在配置文件中配置路由策略了 请求示例:http://localhost:9940/v1/product/list/p

3.2 自定义全局网关过滤器

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("Global自定义过滤执行");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}