Spring Cloud Gateway 详解

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0、Spring Boot2.0 和 Project Reactor 等技术开发的网关组件,旨在为微服务架构提供简单、有效和统一的 API 路由管理方式,同时提供安全性、监控/度量和限流,Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul。

Spring Cloud Gateway 提供的特性(官网):

  • 能够在任何 request 属性上匹配路由(route)
  • Predicates 和 filters 可以作用于特定路由,并且易于编写
  • 集成了 Hystrix Circuit Breaker(断路器)
  • 集成了 Spring Cloud DiscoveryClient(服务发现)
  • 请求速率限制
  • 路径重写

网关的作用

在微服务架构中,各个服务都是独立运行来完成某个特定领域的功能,服务间通过 REST API 或 RPC 进行通信。当前端一个请求发生时,比如查看商品详情,客户端可能要调用商品服务、库存服务、评价服务等多个微服务,如果客户端直接对接各个微服务,在复杂的调用过程中存在的问题:

  1. 客户端需要发起多次请求,增加网络通信成本和客户端处理的复杂性
  2. 服务的鉴权会分布在每个微服务中,存在重复鉴权
  3. 微服务提供的接口协议不同(REST、RPC),客户端要对不同的协议进行适配

网关的出现可以解决这些问题,网关是微服务架构体系对外提供能力的统一接口,本质上是对请求进行转发、前置和后置的过滤

  • 对请求进行一次性鉴权、限流、熔断、日志
  • 统一协议(常见 HTTP),屏蔽后端多种不同的协议
  • 统一错误码处理
  • 请求转发,实现内外网的隔离
  • 通过请求分发规则,实现灰度发布

常见的 API 网关实现方案:OpenResty(Nginx+lua)、Zuul(Netflix)、Gateway(Spring)、Kong

Spring Cloud 已经有了 Zuul,Spring 为什么重新研发了 Gateway?

  1. 替代 Zuul 提供简单高效的 API 网关
  2. Zuul 1.x 采用 thread per connection 方式处理请求(每个请求一个线程进行处理),一旦服务响应慢,线程会被阻塞不释放,性能有一定瓶颈
  3. 虽然 Zuul 2.x 是适合高并发的版本,但是在 Zuul 2.x 开源前 Spring 团队启动了 Gateway

Spring Cloud Gateway 配置

Gateway 配置,maven 依赖

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

application.yml 配置

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          uri: lb://${serviceName}      # http://localhost:8080/
          predicates:
            - Path= /api/**
          filters:
            - StripPrefix=1
  • id – 路由唯一 ID
  • uri – 目标服务地址,支持普通 URL 和 lb://${服务名称}(表示从注册中心获取服务的地址)
  • predicates – 路由条件,匹配请求 URL 判断是否执行该路由
  • filters – 过滤规则,包括 pre 和 post 过滤
    • StripPrefix=1 表示去掉 URL 中的前缀,如果 Path = /api/api/**,设置 StripPrefix=2

Spring Cloud Gateway 并没有依赖 Tomcat,而是用 NettyWebServer 来启动服务监听(从启动日志可以看到)

2020-06-19T15:55:02.698+0800 [INFO] dataworks/dataworks-gateway-service o.s.b.w.e.netty.NettyWebServer - - Netty started on port(s): 6300

Gateway 原理

Spring Cloud Gateway 依赖 Spring Boot 和 Spring Webflux 提供的 Netty runtime(使用非阻塞编程模型),启动时 Netty Server 监听指定端口,接受客户端请求。请求处理过程如下图,几个重要组成部分:

  • 路由(Route),网关的基本组件,由 ID、目标 URI、Predicate 集合和 Filter 集合组成。
  • Predicate,Java 8 引入的函数式接口,提供断言(assert)功能,可以匹配 HTTP 请求中的任何内容,如果 Predicate 集合判断结果是 true,表示请求会由该 Route 进行转发。
  • Filter,为请求提供前置(pre)和后置(post)过滤。

spring-cloud-gateway

Gateway 如何工作

网关的核心是 Filter 以及 Filter Chain,客户端向 Spring Cloud Gateway 发出请求,然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。

Gateway 应用

Predicate 路由匹配

Spring Cloud Gateway 默认提供很多 Route Predicate Factories,分别匹配 HTTP 请求的不同属性,每个 Route 支持多个 Predicate,请求必须同时满足所有的条件才被这个路由匹配。

相关类所在包路径:org.springframework.cloud.gateway.handler.predicate,常用规则如下:

指定时间规则

BeforeRoutePredicateFactory、AfterRoutePredicateFactory、BetweenRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - After=2020-06-01T24:00:00.000+08:00[Asia/Shanghai]    
            # 日期格式必须满足 ZonedDateTime
            # - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

Cookie 匹配规则

CookieRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - Cookie=mode, test         # cookie 中携带 mode=test ,test 支持正则匹配

Header 匹配规则

HeaderRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - Header=X-Request-Id, \d+    # header 中携带 X-Request-Id 为数字,支持正则匹配

Host 匹配规则

HostRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - Host=**.host1.com,**.host2.com     
            # 支持多个 host 匹配,路径命名规则支持 Ant Path,* 匹配任意字符 ** 匹配任意目录 ? 匹配单字符

Method 匹配规则

MethodRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - Method=GET,POST          # 路由 GET 和 POST 请求

Path 匹配规则

PathRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - Path= /api/**,/api/v2/**         # 支持多个 Path 匹配,路径命名规则支持 Ant Path

IP 匹配规则

RemoteAddrRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          predicates:
            - RemoteAddr=192.168.1.1/24             # 设置某个 ip 区间号段的请求才会路由

Query 匹配规则

QueryRoutePredicateFactory 实现

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
            predicates:
            - Query=baz                             # 包含了请求参数 baz的都将被匹配

Filter 路由过滤

Filter 分为前置(Pre)和后置(Post)两种类型:

  • Pre 类型过滤器在请求转发到后端微服务之前执行,在 Pre 过滤器链中可以进行鉴权、限流等操作。
  • Post 类型过滤器在请求执行完成后,将结果返回给客户端之前执行。

Spring Cloud Gateway 中有两种 Filter 实现:GatewayFilter 和 GlobalFilter。GatewayFilter 会应用到单个路由或一组路由上,GlobalFilter 会应用到所有路由上。

相关类所在包路径:org.springframework.cloud.gateway.filter,常用路由方式如下:

GatewayFilter

AddRequestParameterGatewayFilterFactory,为请求添加一个查询参数

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          filters:
            - AddRequestParameter=foo, bar        # 请求增加 foo=bar 这个参数

AddResponseHeaderGatewayFilterFactory,为请求的返回的 Header 中添加数据

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          filters:
            - AddResponseHeader=X-Response-Foo, bar   # Response Header 添加 key=X-Response-Foo, Valuebar

RetryGatewayFilterFactory,请求重试过滤器,当后端服务不可用时,根据配置参数发起重试请求

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          filters:
            - name: Retry
                args: 
                    retries: 3              # 重试次数
                status: 503             # 针对 HTTP 请求返回状态码进行重试

RequestRateLimiterGatewayFilterFactory,对请求进行限流(被限流的请求会收到 Too Many Request)

由 org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter 实现,其他参数参考实现类

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          filters:
            - name: RequestRateLimiter
                args: 
                    redis-rate-limiter.replenishRate: 10                
                    # 令牌桶的令牌填充速度,代表允许每秒执行的请求数
                    redis-rate-limiter.burstCapacity: 20                
                    # 令牌桶的容量,表示每秒用户最大能够执行的请求数量

扩展,高并发限流原理:令牌桶

常用的限流算法有两种:漏桶算法和令牌桶算法。

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,强制限制了速率,突发流量可以以一个稳定的速率进行处理。

令牌桶的思路是,有大小固定的令牌桶,以恒定的速率源源不断地产生令牌(token)。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满,再产生的令牌就会从桶中溢出。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

Google 开源工具包 Guava 提供了限流工具类 RateLimiter,该类基于令牌桶算法来完成限流,可以参考其实现。

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。令牌桶取走 token 是不需要耗费时间的,假设桶内有100个 token 时,那么可以瞬间允许 100 个请求通过,而漏桶按指定速率执行这 100 个请求,漏桶的优势是流出速率平滑。

GlobalFilter

GlobalFilter 作用与 GatewayFilter 相同,但是针对所有路由配置生效,全局过滤链的执行顺序按照 @Order 注解指定的顺序。

LoadBalancerClientFilter,用于实现负载均衡的全局过滤器

spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
            uri: lb://example_service

URI 配置使用 lb://,过滤器会识别到并将 example_service 名称解析成实际访问的主机和端口地址。

自定义 Filter

可以根据实际需求自定义过滤器,支持 GlobalFilter 和 GatewayFilter 两种

自定义 GlobalFilter

实现 GlobalFIlter 接口,框架会自动应用到所有的 Route,同时集成 Order 接口,指定执行顺序

@Component
public class SSOFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ...
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }    
}
自定义 GatewayFilter

首先需要继承 AbstractGatewayFilterFactory

@Service
public class XXXGatewayFilterFactory extends AbstractGatewayFilterFactory<XXXConfig> {

    @Override
    public GatewayFilter apply(final XXXConfig config) {
        return (((exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // ...
            }));
        }));
    }

    @Data
    public static class XXXConfig {

        private String name;
    }
}

需要注意的是:

  • 类名必须以 GatewayFilterFactory 结尾,过滤器名字使用类名前缀
  • apply 方法中,chain.filter() 为 Pre 过滤,then 为 Post 过滤
  • 配置类属性可以再 yml 中配置
  • 该类需要注入到 IoC 容器
spring:
  cloud:
    gateway:
      routes:
        - id: ${serviceId}
          filter:
            - name: XXX
              args: 
                name: ${name}

Gateway 源码

网关初始化

启动注解:@GatewayAutoConfiguration(spring-cloud-gateway-core#org.springframework.cloud.gateway.config)

@Configuration
@ConditionalOnProperty(
  name = {"spring.cloud.gateway.enabled"},
  matchIfMissing = true             // 网关默认开启 spring.cloud.gateway.enabled=false 关闭网关
)
@EnableConfigurationProperties
@AutoConfigureBefore({HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class})
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class,
                     GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass({DispatcherHandler.class})
public class GatewayAutoConfiguration {
}

Spring Cloud Gateway 基于 Spring WebFlux 实现,@GatewayClassPathWarningAutoConfiguration 注解用于用于检查项目是否正确导入 spring-boot-starter-webflux 依赖,而不是错误导入 spring-boot-starter-web 依赖。

@GatewayLoadBalancerClientAutoConfiguration 初始化 LoadBalancerClientFilter 实现负载均衡。

@GatewayAutoConfiguration 中实现多个核心 Bean 的初始化。

Gateway 的配置参数参考 GatewayProperties.class

基本组件

Route

Route 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。

public class Route implements Ordered {
  private final String id;
  private final URI uri;                // 路由指向的目的地 uri
  private final int order;          // 多个 Route 之间的排序,数值越小排序越靠前
  private final AsyncPredicate<ServerWebExchange> predicate;        // 匹配 Route 的条件
  private final List<GatewayFilter> gatewayFilters;                         // 应用于 Route 的过滤器
}

Predicate

Predicate 用于匹配请求和 Route,定义了 3 种逻辑操作方法:and/or/negate

public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
  default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
    // 两个 Predicate 同时满足
  }

  default AsyncPredicate<T> negate() {
    // 对 Predicate 匹配结果取反
  }

  default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
    // 两个 Predicate 只需满足其一
  }
}

Filter

Filter 作用于请求代理之前或之后,最终是通过 filter chain 形成链式调用的,每个 filter 处理完 pre filter 逻辑后委派给 filter chain,filter chain 再委派给下一下 filter。

public interface GatewayFilter extends ShortcutConfigurable {

  Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

public interface GatewayFilterChain {
  Mono<Void> filter(ServerWebExchange exchange);
}

XXXDefinition

RouteDefinition 对 Route 信息进行定义,最终会被 RouteLocator 解析成 Route(类似 BeanDefinition 和 Bean 的关系),FilterDefinition 和 PredicateDefinition 同理。

public class RouteDefinition {
  private String id = UUID.randomUUID().toString();
  private List<PredicateDefinition> predicates = new ArrayList();       // Predicate 定义、描述
  private List<FilterDefinition> filters = new ArrayList();                 // Filter 定义、描述
  private URI uri;                          // 路由目标 URI
}

public class FilterDefinition {         
  private String name;                                                                              // 定义了 Filter 名称,命名规范:为对应的工厂名称前缀
  private Map<String, String> args = new LinkedHashMap();           // 解析配置
}

public class PredicateDefinition {
  private String name;                                                                              // 定义了 Predicate 名称,命名规范:为对应的工厂名称
  private Map<String, String> args = new LinkedHashMap();           // 解析配置
}

XXXFactory

RoutePredicateFactory 生产 Predicate 的工厂,是所有 predicate factory 的顶级接口。GatewayFilterFactory 职责就是生产 GatewayFilter。

public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {
  default Predicate<ServerWebExchange> apply(Consumer<C> consumer) {
  }

  default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {
  }
}

public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {
  default GatewayFilter apply(Consumer<C> consumer) {
  }
}

RouteLocator

用于获取 Route,通过 RouteDefinitionLocator 获取到 RouteDefinition,然后转换成 Route

public interface RouteLocator {
  Flux<Route> getRoutes();
}

public class RouteDefinitionRouteLocator implements RouteLocator, 
        BeanFactoryAware, ApplicationEventPublisherAware {

  private final RouteDefinitionLocator routeDefinitionLocator;
  // RoutePredicateFactory 列表
  private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap();
  // GatewayFilterFactory 列表
  private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap();

  public Flux<Route> getRoutes() {
    return this.routeDefinitionLocator.getRouteDefinitions()
                    .map(this::convertToRoute)
                    .map((route) -> {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("RouteDefinition matched: " + route.getId());
                        }

                        return route;
                    });
  }

  private Route convertToRoute(RouteDefinition routeDefinition) {
    AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);
    List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);
    return ((AsyncBuilder)Route
                .async(routeDefinition)
                .asyncPredicate(predicate)
                .replaceFilters(gatewayFilters))
                .build();
  }
}

路由匹配

Spring WebFlux 的访问入口 org.springframework.web.reactive.DispatcherHandler(对应 MVC 中的 DispatcherServlet)

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
  private List<HandlerMapping> handlerMappings;
  private List<HandlerAdapter> handlerAdapters;

  public Mono<Void> handle(ServerWebExchange exchange) {
    // 顺序使用 handlerMappings 获得对应的 WebHandler,invoke 执行
    return Flux.fromIterable(this.handlerMappings)
      // RoutePredicateHandlerMapping
      .concatMap((mapping) -> { return mapping.getHandler(exchange); })
      .next()
      .switchIfEmpty(this.createNotFoundError())
      // SimpleHandlerAdapter
      .flatMap((handler) -> { return this.invokeHandler(exchange, handler); })
      .flatMap((result) -> { return this.handleResult(exchange, result); });
  }
}

RoutePredicateHandlerMapping 匹配路由

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
  private final FilteringWebHandler webHandler;
  private final RouteLocator routeLocator;

  // mapping.getHandler(exchange); 会调用至此
  protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
    return this.lookupRoute(exchange)    // 匹配 Route
        .flatMap((r) -> {
          // ...
          return Mono.just(this.webHandler);
        })
        .switchIfEmpty(/**未匹配到 Route**/);
  }
}

Filter chain 执行

SimpleHandlerAdapter 循环执行 WebHandler,以 FilteringWebHandler 为例,创建 GatewayFilterChain 处理请求

public class SimpleHandlerAdapter implements HandlerAdapter {
  public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
    WebHandler webHandler = (WebHandler)handler;
    Mono<Void> mono = webHandler.handle(exchange);
    return mono.then(Mono.empty());
  }
}

public class FilteringWebHandler implements WebHandler {
  public Mono<Void> handle(ServerWebExchange exchange) {
      Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
      List<GatewayFilter> gatewayFilters = route.getFilters();
      List<GatewayFilter> combined = new ArrayList(this.globalFilters);
        // 合并 GlobalFilter 和 GatewayFilter,整体排序
      combined.addAll(gatewayFilters);
      AnnotationAwareOrderComparator.sort(combined);

        // 创建 GatewayFilterChain 处理请求
      return (new FilteringWebHandler.DefaultGatewayFilterChain(combined))
                .filter(exchange);
    }
}

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注