使用 Spring Cloud Gateway 进行 API 限流

工程 | Haytham Mohamed | 2021年4月5日 | ...

保护 API 和服务终端免受有害影响(例如拒绝服务、级联故障或资源过度使用)是重要的架构关注点之一。限流是一种控制 API 或服务消费速率的技术。在分布式系统中,集中配置和管理消费者与 API 交互的速率是最佳选择。只有在定义速率内的请求才能到达 API。超出此速率的请求将导致 HTTP “请求过多”错误。

link to rate limit image

Spring Cloud Gateway (SCG) 是一个简单轻量级的组件,但它是管理限制 API 消费速率的有效方式。在这篇博客中,我将通过配置方式说明如何轻松实现这一点。如下图所示,演示包括前端和后端服务,中间是 Spring Cloud Gateway 服务。

link to rate limit image

在架构中包含 SCG 完全不需要编写任何代码。你只需要在普通的 Spring Boot 应用中包含一个 Spring Cloud 依赖 org.springframework.cloud:spring-cloud-starter-gateway,然后进行适当的配置即可开始使用。

SCG 从前端服务接收到的请求可以根据配置的路由定义路由到后端服务。路由定义配置向网关指定请求应如何路由到后端端点。路由配置通常基于可以从 HTTP 请求中提取的信息(例如路径和标头)定义条件。

例如,下面的代码片段列出了一个 YAML 节,用于配置请求应路由到后端服务的条件。它表明当网关接收到路径中包含“/backend”的请求时,应将其路由到后端服务。在配置中,为该路由指定了一个标识符和后端服务的 URI。

spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://localhost:8081
          predicates:
            - Path=/backend

RequestRateLimiter 是 SCG 提供的众多网关过滤器之一。它的实现决定了请求是允许继续还是已超出限制。该实现允许你(可选地)插入一个键来管理限制不同服务的请求数量。虽然可以自定义如何解析键,但网关自带了一个使用用户 Principal 名称的解析器。需要一个安全的网关来解析用户的 principal 名称,但你可以选择实现 KeyResolver 接口以从 ServerWebExchange 中解析不同的键。你可以使用 SPEL 表达式 #{@customKeyResolver} 在配置中指向一个自定义的 KeyResolver bean(例如,名为 customKeyResolver)。下面的列表显示了 KeyResolver 接口

public interface KeyResolver {
    Mono<String> resolve(ServerWebExchange exchange);
}

如果没有解析到键,网关将拒绝请求。要让网关接受未解析到键的情况,你可以设置以下属性

spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key=false

你还可以通过设置以下属性来指定网关在无法确定键时应报告的状态码

spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code=

考虑一个蓝图架构,其中网关使用 Redis 控制 API 消费的限制。提供的 Redis 实现使用了令牌桶算法。要使用它,你需要在网关应用中包含 spring-boot-starter-data-redis Spring Boot starter 依赖。令牌桶算法基本上使用平衡令牌作为一种手段来维护一个累积的使用预算。该算法假设令牌会以一定的速率添加到桶中,而对 API 的调用会从这些令牌中消耗。一次 API 调用可能会执行许多操作来组合响应以满足请求(想想基于 GraphQL 的 API)。在这种情况下,该算法有助于识别一次调用可能会让 API 消耗不止一个令牌。

link to rate limit image

提供的 Redis 实现允许你定义用户在特定时间段内可以发出请求的速率。它还使得在受定义消费速率约束的同时满足零星需求成为可能。例如,可以通过设置属性 redis-rate-limiter.replenishRate=500 定义每秒 500 个请求的补充速率,并通过设置属性 redis-rate-limiter.burstCapacity=1000 定义每秒 1000 个请求的突发容量。这样做将消费限制为每秒 500 个请求。如果出现请求突发,只允许 1000 个请求。然而,由于 1000 个请求是 2 秒的配额,网关在接下来的第二秒将不再路由请求。配置还允许你通过设置属性 redis-rate-limiter.requestedTokens 来定义一个请求会消耗多少个令牌。通常,它设置为 1。

要使用具有请求限制功能的网关,需要配置 RequestRateLimiter 网关过滤器。配置可以指定参数来定义补充速率、突发容量以及请求消耗的令牌数量。下面的示例演示了如何使用这些参数配置网关

spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://localhost:8081
          predicates:
            - Path=/backend
          filters:
          - name: RequestRateLimiter
            args:
              redis-rate-limiter.replenishRate: 500
              redis-rate-limiter.burstCapacity: 1000
              redis-rate-limiter.requestedTokens: 1

Spring Cloud Gateway 提供了灵活性,允许你定义自己的自定义限流器实现。它提供了一个 RateLimiter 接口供实现并定义为一个 bean。限流器 bean 可以像自定义键解析器一样,使用 SPEL 表达式进行配置。例如,你可以定义一个名为 customRateLimiter 的自定义限流器 bean 和一个名为 customKeyResolver 的自定义键解析器,然后像这样配置路由

@Bean
public KeyResolver customKeyResolver {
	return exchange -> ....  // returns a Mono of String
}
spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://localhost:8081
          predicates:
            - Path=/backend
          filters:
          - name: RequestRateLimiter
            args:
              rate-limiter: "#{customRateLimiter}"
              key-resolver: "#{customKeyResolver}"

一个示例可在 GitHub 上获取

订阅 Spring 动态

保持与 Spring 动态的连接

订阅

抢占先机

VMware 提供培训和认证,助你加速进步。

了解更多

获得支持

Tanzu Spring 通过一个简单的订阅提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部