SpringCloud 13 —— 网关之Gateway (下) 路由配置

Gateway 禁用过滤器

如果想禁用GateWay,可以通过两种方式。

  1. 如果是代码bean配置将配置的Bean注释掉即可。
//    @Bean
//    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
//        return builder.routes()
//                .route(r -> r.path("/gateway/**")
//                        .filters(f -> f.stripPrefix(1)
//                                .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
//                        .uri("lb://ribbon-server")
//                        .order(0)
//                        .id("strippath_route")
//                )
//                .build();
//    }
  1. 如果是配置文件配置,
spring:
  cloud:
    gateway:
      enabled: false

Gateway 路由前缀

前面我们在前面讲Zuul的之后知道Zuul的路由前缀配置方式很简单,只需要
设置zuul.prefix即可。

而在Spring Cloud Getaway 为我们提供了很多原生的过滤器,我们可以通过StripPrefix、RewritePath 在默认过滤器中 进行前缀的设置,可以参照过滤器中 StripPrefix、过滤器 RewritePath、过滤器 Default 进行实现。

Gateway 路由跳转

前面我们在前面讲解Zuul的时候,路由跳转是通过在url中指定forward:进行跳转。不清楚的可以回头看看。

Spring Cloud Getaway 的路由跳转也可以使用forward:,使用方式一样的。比如下面我们要讲的Gateway FallBack 就是使用forward:处理异常问题

Gateway 限流

在前面网关扩展之zuul限流中我们介绍了两种限流手段,可以很方便实现。如果忘了的话可以回去看一下。

在 Spring Cloud Gateway 上实现限流是个不错的选择,只需要编写一个过滤器就可以了。有了前边过滤器的基础,写起来很轻松。Spring Cloud Gateway 已经内置了一个RequestRateLimiterGatewayFilterFactory,我们可以直接使用。

目前RequestRateLimiterGatewayFilterFactory的实现依赖于 Redis,Redis的实现基于Stripe的工作。它需要使用spring-boot-starter-data-redis-reactive Spring Boot启动器。

使用的算法是令牌桶算法。

Getaway实现限流所使用的lua脚本在gateway包下的script目录中,如下:

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)

--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)

local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
  new_tokens = filled_tokens - requested
  allowed_num = 1
end

--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)

redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)

return { allowed_num, new_tokens }

新建项目模块cloud-gateways-gateway,引入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

添加配置

server:
  port: 8711
spring:
  application:
    name: gateways-gateway
  profiles:
    active: rate_limiter_route
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
---
spring:
  cloud:
    gateway:
      routes:
        - id: rate_limiter_route
          uri: lb://ribbon-server
          predicates:
            - After=2020-05-09T11:30:11.965+08:00[Asia/Shanghai]
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                key-resolver: "#{@userKeyResolver}"
  redis:
    host: ${redis.host}
    password: ${redis.pwd}
    port: 6379
  profiles: rate_limiter_route

限流配置描述

  • filter 名称必须是 RequestRateLimiter
  • redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求
  • redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  • key-resolver:使用 SpEL 按名称引用 bean

新建启动类

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

限流Bean 配置

下面的配置给出了三种,分别是基于参数、基于ip、基于请求路径。用的时候只能用一个。

@Configuration
public class GatewayConfig {
    //基于参数
    @Bean
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("cloud"));
    }
    // 基于ip
//    @Bean
//    public KeyResolver ipKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
//    }
      //基于请求路径
//    @Bean
//    KeyResolver apiKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getPath().value());
//    }
    
}

启动测试,多次频繁点击发送。可以看到返回429 Too Many Request。

Gateway 重试

前面我们在将Feign、Ribbon的时候已经提到过重试机制,就是在我们发送Http 请求的时候希望一次失败后可以再尝试发送一次,因为前几次可能因为网络等不确定原因。Spring Cloud Gateway对请求重试提供的一个GatewayFilter Factory

添加并active配置

---
spring:
  cloud:
    gateway:
      routes:
        - id: retry_route
          uri: lb://ribbon-server
          predicates:
            - After=2020-05-09T11:30:11.965+08:00[Asia/Shanghai]
          filters:
            - name: Retry
              args:
                retries: 3
                series:
                  - SERVER_ERROR
                statuses:
                  - INTERNAL_SERVER_ERROR
                  - BAD_GATEWAY
                methods:
                  - GET
                exceptions:
                  - java.io.IOException
                  - java.util.concurrent.TimeoutException
  profiles: retry_route

Spring Cloud Gateway 通过以下参数来控制重试机制: 。

  • retries:重试次数,默认值是 3 次

  • statuses:HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus

  • methods:指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法,取值参考:org.springframework.http.HttpMethod

  • series:一些列的状态码配置,取值参考:org.springframework.http.HttpStatus.Series。符合的某段状态码才会进行重试逻辑,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5 个值。

  • exceptions:应重试的引发异常的列表。

  • backoff:为重试配置的指数补偿。重试在的退避间隔后执行firstBackoff * (factorn),其中n为迭代。如果maxBackoff已配置,则应用的最大退避限制为maxBackoff。如果basedOnPreviousValue为true,则使用计算退避prevBackoff * factor。

源码分析

public static class RetryConfi