Spring Cloud Gateway 技术介绍

Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的高性能响应式网关,由 Spring Cloud 官方提供支持。本文对 API 网关和 Spring Cloud Gateway 的概念做了简单介绍,并从快速开始、路由谓词工厂配置、网关过滤器工厂配置、流控降级方案等方面介绍该技术的应用方式。

API 网关介绍

在微服务系统中,网关就是把各个服务对外的 API 汇聚起来,让外界看起来是一个统一的接口,同时可以在网关中提供一些额外功能。简单来说,网关是所有项目的统一入口

网关功能

  • 关注稳定和安全
    • 流量控制
    • 日志统计
    • 防止 SQL 注入
    • 防止 Web 攻击
    • 屏蔽工具扫描
    • 黑白名单
    • 证书/加解密处理
  • 提供服务
    • 流量控制
    • 服务降级、熔断
    • 负载均衡、灰度策略
    • 服务过滤、聚合、发现
    • 权限验证、用户等级策略
    • 业务规则与参数校验
    • 多级缓存

网关组成

  • 路由转发
  • 过滤器

网关解决方案

  • Spring Cloud Netflix Zuul
  • Spring Cloud Gateway

Spring Cloud Gateway

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

Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式网关,不能部署在传统的 servlet 容器中工作,也不能构建成 war 包。

核心概念

  • 路由(Route):
    一个路由是由 ID、URI、Predicate 集合、Filter 集合组成的。
  • 断言/谓词(Predicate):
    断言就是一些附加条件和内容,用以匹配请求头和参数等信息。
  • 过滤器(Filter):
    过滤器是对请求/相应的任意处理,Spring Cloud Gateway 已经定义好 25 种过滤器,可以满足绝大多数需求。

主要架构

image-20231104112556353

Gateway 快速入门

  1. 导入依赖:

        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
        </dependencies>
    
  2. 配置 application.yml

    server:
      port: 8888
    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes: # 是一个 RouteDefinition 的 list,用 - 标识一个对象,或者用 , 分隔
            - id: order_route  # 路由的唯一标识,路由到订单服务
              uri: http://localhost:8080/ # 目标服务的地址
              predicates: Path=/order-service/** # 断言,匹配请求的 URL
              filters: # 过滤器,可以理解为过滤器链,每个过滤器都可以对请求进行处理
                - StripPrefix=1 # 去掉请求路径的前缀,StripPrefix=1 表示去掉前缀为 /order-service 的路径
              # 通过上面的配置,将 http://localhost:8888/order-service/order/1
              # 转发到 http://localhost:8080/order/1
    

整合 Nacos

  1. 导入依赖:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-loadbalancer</artifactId>
            </dependency>
    
  2. 配置 application.yml

    server:
      port: 8888
    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes: # 是一个 RouteDefinition 的 list,用 - 标识一个对象,或者用 , 分隔
            - id: order_route  # 路由的唯一标识,路由到订单服务
              uri: lb://order-service # 目标服务的地址,使用 lb 负载均衡,需要在服务端和调用端都引入 loadbalancer
              predicates: Path=/order-service/** # 断言,匹配请求的 URL
              filters: # 过滤器,可以理解为过滤器链,每个过滤器都可以对请求进行处理
                - StripPrefix=1 # 去掉请求路径的前缀,StripPrefix=1 表示去掉前缀为 /order-service 的路径
              # 通过上面的配置,将 http://localhost:8888/order-service/order/1
              # 转发到 http://localhost:8080/order/1
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
    #        username:
    #        password:
    

    或者,使用简写形式,启用自动识别服务(基于注册中心),很简便,但不够灵活。

    server:
    port: 8888
    spring:
    application:
     name: gateway-demo
    cloud:
     gateway:
       discovery:
         locator:
           enabled: true # 启用自动发现服务
     nacos:
       discovery:
         server-addr: 127.0.0.1:8848
    #        username:
    #        password:
    

Spring Cloud Gateway 的配置

路由谓词工厂(Route Predicate Factories)配置

这里介绍的路由谓词工厂详细解释可以参考 官方文档

Spring Cloud Gateway 之 Filter - 编程语言 - 亿速云

基于 Datetime 的谓词工厂

根据时间(ZonedDatetime)做判断,主要包含:

  1. AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期。

    predicates:
    - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  2. BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期。

    predicates:
    - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  3. BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内。

    predicates:
    - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    
        predicates:
        - Cookie=chocolate,ch.p # 这里支持正则表达式

基于请求头的谓词工厂

        predicates:
        - Header=X-Request-Id,\d+

基于主机名的谓词工厂

        predicates:
        - Host=**.somehost.org,**.anotherhost.org

基于请求方式的谓词工厂

        predicates:
        - Method=GET,POST

基于路径的谓词工厂

        predicates:
        - Path=/red/{segment},/blue/{segment} # 这里的 {segment} 会提取值到 segment 变量中供工厂使用

基于查询参数的谓词工厂

        predicates:
        - Query=green # 匹配参数包含 green 的请求
        predicates:
        - Query=red,gree. # 匹配参数包含 red 且值匹配 gree. 的请求

基于 IP 的谓词工厂

        predicates:
        - RemoteAddr=192.168.1.1/24 # 匹配 IP + 掩码指定的所有地址

基于权重的谓词工厂

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8 # 权重 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2 # 权重 2

自定义谓词工厂

Spring Cloud Gateway 允许开发者自定义谓词工厂,要求:

  1. 必须作为 Spring Bean 被实例化
  2. 命名必须为XXXRoutePredicateFactory
  3. 必须实现 AbstractRoutePredicateFactory
  4. 必须声明静态内部类,声明属性接收配置

关于自定义谓词工厂的实现,可以参考 org.springframework.cloud.gateway.handler.predicate 包下的实现。

网关过滤器工厂(Gateway Filter Factory)配置

由于网关过滤器工厂数量很多,建议查看 官方文档,这里只给出几个常用的过滤器。

更多过滤器可以查看:org.springframework.cloud.gateway.filter.factory

添加请求头

        filters:
        - AddRequestHeader=X-Request-red, blue
        filters:
        - AddRequestHeadersIfNotPresent=X-Request-Color-1:blue,X-Request-Color-2:green

添加请求参数

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        predicates:
        - Host: {segment}.myhost.org
        filters:
        - AddRequestParameter=foo, bar-{segment}

添加响应头

        filters:
        - AddResponseHeader=X-Response-Red, Blue

添加前缀

        filters:
        - PrefixPath=/mall-order # 转发时添加前缀,对应的微服务需要配置 context-path

重定向

        filters:
        - RedirectTo=302, https://acme.org

自定义过滤器

Spring Cloud Gateway 允许开发者自定义网关过滤器,要求:

  1. 必须作为 Spring Bean 被实例化
  2. 命名必须为 XXXGatewayFilterFactory
  3. 必须实现 AbstractGatewayFilterFactory
  4. 必须声明静态内部类,声明属性接收配置

关于自定义网关过滤器工厂的实现,可以参考 org.springframework.cloud.gateway.filter.factory 包下的实现。

全局过滤器(Global Filter)配置

与网关过滤器工厂不同,全局过滤器(Global Filter)对全局生效,是通过配置 Bean 生效的,不需要针对请求分别配置。Spring Cloud Gateway 已经提供了多种全局过滤器,具体配置方式详见 官方文档

Spring Cloud Gateway 之 Filter - 编程语言 - 亿速云

自定义全局过滤器

Spring Cloud Gateway 允许开发者自定义全局过滤器,要求:

  1. 必须作为 Spring Bean 被实例化
  2. 命名必须为 XXXFilter
  3. 必须实现 GlobalFilter

Reactor Netty 访问日志

要启用 Reactor Netty 访问日志,需要设置 -Dreactor.netty.http.server.accessLogEnabled=true

注意:它必须是 Java 环境变量,而不是 Spring Boot 应用配置。

logback 配置

image-20231116222404143

CORS 跨域配置

跨域资源共享(Cross-Origin Resource Sharing,CROS)指的是一种基于 HTTP 头的机制,通过允许浏览器访问服务器标示的其他源的资源,为了安全起见,CORS 默认关闭。在 Spring Cloud Gateway 可以通过以下方式配置 CORS:

spring:
  cloud:
    gateway:
      globalcors: # 跨域配置
        cors-configurations:
          '[/**]': # 允许跨域访问的资源
            allowedOrigins: "https://docs.spring.io" # 允许跨域访问的来源
            allowedMethods: # 允许的方法
            - GET

除了配置全局的 CORS,Spring Cloud Gateway 允许使用路由 CORS,通过以下方式进行配置:

spring:
  cloud:
    gateway:
      routes:
      - id: cors_route
        uri: https://example.org
        predicates:
        - Path=/service/**
        metadata:
          cors
            allowedOrigins: '*'
            allowedMethods:
              - GET
              - POST
            allowedHeaders: '*'
            maxAge: 30

整合 sentinel 流控降级

Sentinel 官网:「Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件」。

  1. 官网发布页 下载 sentinel-dashboard-x.y.z.jar,并运行

    java -jar sentinel-dashboard-x.y.z.jar
    

    默认绑定端口 8080,用户名 sentinel,密码 sentinel,此时控制台空空如也:

    image-20231117142355441
  2. 导入依赖

            <!-- sentinel 整合 gateway -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            </dependency>
            <!-- sentinel -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
  3. 配置 sentinel 控制台

    spring:
      cloud:
        sentinel:
          transport:
            dashboard: 127.0.0.1:8858
    
  4. 启动项目,再次打开控制台,可以看到网关模块已经被加载:

    image-20231117143307030

    注意:由于 sentinel 是懒加载的,需要先对网关发送一次请求,才能加载出信息。

  5. 通过控制台的请求链路-流控选项设置流控规则:

    image-20231117143724384

    也可以通过 GatewayRuleManager.loadRules() 配置流控规则。

  6. 通过控制台的请求链路-降级选项设置降级规则:

    image-20231117144657726
  7. 可以通过 GatewayCallbackManager.setBlockHandler() 配置异常返回(统一返回体):

    image-20231117145901320

网关的高可用

只需要部署多台实例,并使用 nginx 反向代理、负载均衡即可。