spring cloud gateway详解

312 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

gateway构建在Spring5、SpringBoot2和Reactor基础之上。
gateway 和zuul都是常用的网关,目前zuul已经进入维护阶段,spring官方开发gateway,和zuul整体比较有很大优势,具体自行了解,当然,这两个组件都很强大,具体不再介绍。但是gateway是基于Reactor的,所以在使用时和spring.*.web 组件冲突,这点需要注意。

前言

Spring Cloud中集成了很多常用的功能,如服务、降级和熔断等,如果要集成Spring Cloud的功能,首先要注意的就是版本问题:

Spring Cloud和Spring Boot的版本对应表

使用Spring Cloud Gateway

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

如果想禁用Gateway,可使用spring.cloud.gateway.enabled=false

基本组件

  • Route: 路由,一个完整的Route由id、uri、predicates和filters构成,当predicates匹配当前请求时,该Route生效;
  • Predicate: 谓词,判断某个请求是否命中该Route,是一个参数为 Spring Framework ServerWebExchange的类型Java 8 Function Predicate
  • Filter: 当匹配到Route后,这些过滤器将应用到当前请求,执行顺序用Ordered控制。

工作流程

  1. Gateway Client发起一个请求;
  2. 如果Gateway Handler Mapping匹配当前请求,会转发到 Gateway Web Handler进行处理;
  3. Gateway Web Handler会按顺序执行 filters,并把结果向上返回;

filters分pre和post,其中pre在实际业务服务之前执行,post是在实际业务服务之后执行。

注意事项

Gateway是构建在Spring Boot 2.x、Spring webflux和Reactor之上,这就意味着和我们常用的Spring-boot-starter-web并不兼容,所以像Spring data、Spring Security等组件也不兼容。

Predicate:谓词,用于判断当前请求是否命中该Route

定义方式

定义谓词有两种方式:完整定义和快捷定义。

完整定义

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://imdony.org
        predicates:
        - name: Cookie
          args:
            name: mycookie
            regexp: mycookievalue

predicates是一个数组,包括两个key:name和args:

  • name表示该predicate的类型(即采用哪种匹配策略,示例中表示判断Cookie),
  • args是判断的参数,是一个key-value映射表(示例中表示要包括Cookie键值是mycookie=mycookievalue才会命中该Route。

快捷定义

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Cookie=mycookie,mycookievalue

快捷方式省略了name和args两个key,直接用name=args的方式把两部分连接起来,其中args部分第一个表示args中的name,逗号(,)后面表示regexp。

预置的Predicate

Spring Cloud Gateway预置了很多可以直接使用的Predicate,而且可以组合使用。

After:在某个时间点(ZonedDateTime)之后的请求会命中该Route

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://imdony.org
        predicates:
        - After=2017-01-20T17:42:47[Asia/Shanghai]

2017-01-20 17:42:47之后所有的请求,都将转调 imdony.org,可用于控制规划新服务上线时间。

Before:在某个时间点(ZonedDateTime)之前的请求会命中该Route

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47[Asia/Shanghai]

2017-01-20 17:42:47之前所有的请求,都将转调 imdony.org,可用于控制服务下线时间。

Between:在某时间之间的请求会命中该Route

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47[Asia/Shanghai], 2017-01-21T17:42:47[Asia/Shanghai]

2017-01-20 17:42:47和2017-01-21 17:42:47之间所有的请求,都将转调 imdony.org,可用于控制服务临时扩容。

Cookie:匹配请求中Cookie内容

包括两个参数name和regexp,其中regexp是一个 Java regular expression。

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://imdony.org
        predicates:
        - Cookie=name,ch-dony

当Cookie中存在name=ch-dony时,将调用 imdony.org,可对客户端进行分批控制。

Header:匹配请求的Header参数

包括两个参数name和regexp,其中regexp参照Cookie。

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://imdony.org
        predicates:
        - Header=X-Request-Id, \d+

当Header中的X-Request-Id匹配\d+时,命中该Route。

Host:匹配请求Header中的Host参数

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://imdony.org
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

Host是一个列表,用,隔开,表示匹配其中一个就可以。

在该谓词中支持使用变量,如下:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://imdony.org
        predicates:
        - Host={sub}.somehost.org,**.anotherhost.org

该谓词会提取其中的{sub}(如果Host=www.somehost.org,那么sub=www),并把sub匹配的值应用到GatewayFilter中去(该实例中未使用Filter),在GatewayFilter中使用ServerWebExchange.getAttributy("uriTemplateVariables")获取匹配的值,其中为了方便,官方提供了工具类ServerWebExchangeUtils,如ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值就是uriTemplateVariables。

写一个自定义 过滤器的 例子

//自定义一个GatewayFilterFactory,可是使用AbstractNameValueGatewayFilterFactory ,方便
//要添加@component,注册为Bean,启动时会调用该Factory并生成一个GatewayFilter加入Failter chain
@Component
    public class PathTestFilter extends AbstractNameValueGatewayFilterFactory {

        @Override
        public GatewayFilter apply(NameValueConfig config) {
            return new GatewayFilter() {
                @Override
                public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                    String val= ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
                    return chain.filter(exchange);
                }
            };
        }
    }      
# 把上面的Filter添加到某路由的Filter chain
routes:
	- id: user-route
      uri: http://localhost:8080
      predicates:
        - Path=/api/{sub}
      filters:
        - PathTestFilter=args,{sub}

RemoteAddr:匹配IP地址

和Host功能差不多,一个对应主机一个对应IP(例如192.168.0.1/16,16是子网掩码)。

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://imdony.org
        predicates:
        - RemoteAddr=192.168.1.1/24

如果客户端使用了代理服务器,那么gateway无法获取到代理服务器后面的客户端IP,gateway提供了解决方案:基于 X-Forwarded-For header的RemoteAddressResolver:XForwardedRemoteAddressResolver,详细的稍后介绍。

Method:匹配请求的类型

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://imdony.org
        predicates:
        - Method=GET,POST

当使用GET或POST访问时,命中该Route。

Path:匹配请求的Path(Uri)

包括两个参数:PathMatcher列表和matchTrailingSlash(以斜线结尾,可选,默认true),以下示例会匹配/red/1 或/red/1/(如果matchTrailingSlash=false,该项不会被匹配) 或 /red/blue 或 /blue/green:

追加说明

  1. 配置uri时,一定要完整,如http://localhost:8080,不能省略schema部分,即localhost:8080会路由失败,具体可见RouteToRequestUrlFilter源码中的filter方法;
  2. 在新版(测试版本cloud 2021.0.1和boot 2.6.7)中,Path=/api/**匹配的前缀/api不再自动删除,需添加 filters:-StripPrefix=1手动删除;
spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://imdony.org
        predicates:
        - Path=/red/{segment},/blue/{segment}

变量{segment}参照Host,另外还有一个简单的方法获取该参数:

Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
String segment = uriVariables.get("segment");

Query:匹配GET请求的参数

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://imdony.org
        predicates:
        - Query=red, gree.

当请求中存在param是red并且值匹配gree.时命中,如green和greet。

Weight :分组匹配权重

weight有两个参数:group和weight,在每一个分组内计算权重,下面例子中分了一个组group1,其中第一个权重80%,第二个权重20%,请求量根据权重分配到不同的uri中。

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

如果你的服务器性价比比较明显,可以采用这种策略进行权重的划分,以减轻高负载服务器的压力。

整合Hystrix降级和限流

  • 降级:被调用的方法出现异常时,调用方不用一直阻塞等着,直接调用降级方法,防止一直阻塞造成服务雪崩;
  • 限流:根据不同的维度(如统一客户IP、同一用户ID等),限制调用频率,Hystrix采用令牌桶;

整合降级

添加依赖

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

添加配置

  cloud:
    gateway:
      routes:
        - id: user-route
          uri: http://localhost:8080
          predicates:
            - Path=/api/**
          filters:
            - name: Hystrix
              args:
                name: fallbackCmdA
                fallbackUri: forward:/fallbackA

在filters中添加name:Hystrix,就会把降级过滤器添加到当前router的过滤器链中。

  • 规则1:name:Hystrix 激活的过滤器是HystrixGatewayFilterFactory
  • 规则2:args:name:fallbackCmdA这个是随便写的名字
  • 规则3:fallbaclUri:forward:/fallbackA,fallbackA是需要自定义的Bean

正常Hystrix默认超时时间是 1000ms,即1s,可以通过以下方式修改超时间:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

添加自定义Bean

@RestController
public class FallbackController {
	// 这个名字就是fallbackUri中使用的名字,要对应,正常服务异常会被降级到该接口方法
    @GetMapping("/fallbackA")
    public String fallback(){
        System.out.println("call fail:"+new Date());
        return "call fail";
    }
}

添加限流

概念性的内容,大家自己查查吧

添加依赖

参考 #整合降级 节点中的 依赖,是一样的

添加配置

    gateway:
      routes:
        - id: user-route
          uri: http://localhost:8080
          predicates:
            - Path=/api/**
          filters:
            - name: Hystrix
              args:
                name: fallbackCmdA
                fallbackUri: forward:/fallbackA
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@keyResolver}'
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1

在filters中添加name:RequestRateLimiter,即生效限流功能

  • 规则1:name:RequestRateLimiter会把过滤器RequestRateLimiterGatewayFilterFactory(实际上是工厂)添加到过滤器链
  • 规则2:key-resolver后面是引用的Bean,即采用哪个维度去限流,如用户ID,客户端IP之类,需要自定义
  • 规则3:redis-rate-limiter.replenishRate:配置1s生成几个令牌
  • 规则4:redis-rate-limiter.burstCapacity:令牌桶的容量是多少,用完了新请求就要等待了

添加维度/规则Bean

    @Bean
    KeyResolver keyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }