9.3.3 配置请求限流器

根据需求,你可以为特定路由配置 RequestRateLimiter 过滤器,或将其配置为默认过滤器。在这种情况下,我们将它配置为默认过滤器,以便它应用于所有路由(当前和未来的路由)。

RequestRateLimiter 在 Redis 上的实现基于令牌桶算法。每个用户被分配一个桶,桶中的令牌以特定速率(补充速率)随时间滴入。每个桶都有最大容量(突发容量)。当用户发出请求时,会从其桶中移除一个令牌。当没有剩余令牌时,请求不被允许,用户必须等待更多令牌滴入其桶中。

注意: 如果你想了解更多关于令牌桶算法的信息,我建议阅读 Paul Tarjan 的"Scaling your API with Rate Limiters"文章,了解他们如何在 Stripe 使用它来实现限流器(https://stripe.com/blog/rate-limiters)。

对于这个示例,让我们配置算法使每个请求花费 1 个令牌(redis-rate-limiter.requestedTokens)。令牌按照配置的补充速率(redis-rate-limiter.replenishRate)滴入桶中,我们将设置为每秒 10 个令牌。有时可能会出现峰值,导致比平时更多的请求。你可以通过为桶定义更大的容量(redis-rate-limiter.burstCapacity),例如 20,来允许临时突发。这意味着当发生峰值时,每秒允许最多 20 个请求。由于补充速率低于突发容量,因此不允许后续突发。如果两个峰值连续发生,只有第一个会成功,而第二个将导致一些请求以 HTTP 429 - Too Many Requests 响应被丢弃。application.yml 文件中的最终配置如下所示。

代码清单 9.14 将请求限流器配置为网关过滤器

spring:
 cloud:
 gateway:
 default-filters:
 - name: RequestRateLimiter
 args:
 redis-rate-limiter:
 replenishRate: 10 # 每秒滴入桶中的令牌数量
 burstCapacity: 20 # 允许最多 20 个请求的突发
 requestedTokens: 1 # 一个请求花费多少令牌

对于为请求限流器得出合适的数字,没有通用规则可循。你应该从应用程序需求开始,采用试错方法:分析生产流量,调整配置,然后重复执行,直到实现既保持系统可用又不影响用户体验的设置。即使在那之后,你也应该继续监控限流器的状态,因为情况可能会在未来发生变化。

Spring Cloud Gateway 依赖 Redis 来跟踪每秒发生的请求数量。默认情况下,每个用户被分配一个桶。然而,我们还没有引入认证机制,因此在我们在第 11 章和第 12 章解决安全问题之前,我们将对所有请求使用单个桶。

注意: 如果 Redis 变得不可用会发生什么?Spring Cloud Gateway 在构建时考虑了弹性,因此它将保持其服务水平,但限流器将被禁用,直到 Redis 重新启动并运行。

RequestRateLimiter 过滤器依赖 KeyResolver bean 来确定每个请求使用哪个桶。默认情况下,它使用 Spring Security 中当前经过身份验证的用户。在我们为 Edge Service 添加安全性之前,我们将定义一个自定义 KeyResolver bean,并使其返回常量值(例如 anonymous),以便所有请求都将映射到同一个桶。

在你的 Edge Service 项目中,在新的 com.polarbookshop.edgeservice.config 包中创建一个 RateLimiterConfig 类,并声明一个 KeyResolver bean,实现返回常量密钥的策略。

代码清单 9.15 定义解析每个请求使用哪个桶的策略

package com.polarbookshop.edgeservice.config;

import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RateLimiterConfig {

 @Bean
 public KeyResolver keyResolver() {
 return exchange -> Mono.just("anonymous"); // 使用常量密钥进行限流
 }
}

Spring Cloud Gateway 配置为在每个 HTTP 响应中附加有关限流的详细信息头,我们可以使用它来验证其行为。重新构建并运行 Edge Service(./gradlew bootRun),然后尝试调用其中一个端点。

$ http :9000/books

响应体取决于 Catalog Service 是否运行,但这在本示例中并不重要。需要注意的有趣方面是响应的 HTTP 头。它们显示了限流器的配置和时间窗口(1 秒)内允许的剩余请求数量:

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Burst-Capacity: 20
X-RateLimit-Remaining: 19
X-RateLimit-Replenish-Rate: 10
X-RateLimit-Requested-Tokens: 1

你可能不想向客户端暴露此信息,以防信息可能帮助恶意行为者针对你的系统发起攻击。或者你可能需要不同的头名称。无论哪种方式,你都可以使用 spring.cloud.gateway.redis-ratelimiter 属性组来配置该行为。测试完应用程序后,使用 Ctrl-C 停止它。

注意: 当限流器模式与其他模式(如时间限制器、断路器和重试)组合时,限流器首先被应用。如果用户的请求超过限制,它会立即被拒绝。

Redis 是一种高效的数据存储,确保快速数据访问、高可用性和弹性。在本节中,我们使用它为限流器提供存储,下一节将向你展示如何在另一个常见场景中使用它:会话管理。

results matching ""

    No results matching ""