设想这样一种情况:某个行为异常的客户端每秒发送 1,000 个请求,后端迅速饱和;下游服务失去响应;队列不断堆积,最终引发级联故障。你该如何保护 API 网关,使其免受流量高峰和服务故障的冲击?两种经过验证的模式可以提供帮助:速率限制与断路器。GitHub 将已认证 API 用户限制为每小时 5,000 次请求。断路器则会在服务发生故障时停止继续向其发送请求。当客户端超出限制时,服务器会返回 HTTP 429。本指南将介绍如何使用令牌桶算法和 Redis 计数器来配置速率限制,并实现一个具备 Closed、Open 和 Half-Open 三种状态的断路器。读完后,你将能够通过 YAML 或代码在网关上配置这两种策略,防止系统过载并实现优雅恢复。

速率限制与断路器的基础原理

速率限制和断路器解决的是不同的问题,而一个具备韧性的 API 网关通常两者都需要。速率限制控制进入系统的流量规模;断路器则决定请求是否还应该到达已经出现故障的依赖服务。下表展示了二者的核心区别。

维度速率限制断路器
主要目标控制进入系统的请求量防止下游依赖故障带来更大影响
作用方向入站(客户端到 API)出站(API 到下游服务)
触发条件请求数超过阈值下游失败率超过阈值
典型响应返回带重试提示的 HTTP 429立即失败或返回降级结果
状态模型无状态:基于计数器和时间窗口有状态:Closed、Open、Half-Open

何时使用速率限制

当某个客户端可能抢占其他客户端资源时,就应该使用速率限制。一个拥有大量使用者的公共 API 需要公平访问,因此你可以在时间窗口内限制每个客户端的请求数。同样的逻辑也适用于保护登录接口免受暴力破解,以及阻止爬虫耗尽你的系统容量。

你还应通过配置速率限制来控制成本。突发的使用高峰可能会迅速消耗基础设施预算。根据 API key、OAuth 令牌或使用套餐设置限制,可以让你为免费套餐提供较低上限,为付费套餐提供更高吞吐能力。这样可以让队列保持稳定,也让所有用户的延迟更可预测。

何时使用断路器

当下游服务开始失败时,就该启用断路器模式。调用外部 ML API、支付处理服务或库存服务时,都可能发生超时并长时间占用线程。如果没有保护机制,每个请求都会一直等待直到超时,连接池被耗尽,故障也会在微服务之间扩散。

断路器模式会监控失败率升高、慢调用和超时等现象。一旦失败超过阈值,断路器就会切换到 Open 状态,并立即返回降级响应。经过一段冷却时间后,它会进入 Half-Open 状态,并以有限请求测试服务是否恢复。这个状态机既能隔离故障,也能支持自动恢复。在微服务架构中,只要某个不稳定依赖可能引发级联故障,就应考虑使用它。

在 API 网关上配置速率限制

现实中的 API 已经展示了速率限制的典型方式。GitHub 将已认证用户限制为每小时 5,000 次请求。这些数值反映了各平台自身的容量与公平性目标。你也可以在自己的 API 网关上用类似逻辑配置速率限制。网关会统计传入请求,一旦某个客户端超过阈值,就拒绝其额外请求。

Redis 常被用于存储这些请求计数器。集中式存储可以让多个网关实例之间保持计数一致。当客户端超过限制时,网关会返回 HTTP 429 Too Many Requests。响应中还可以包含 X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset 等头信息,用于告知客户端何时可以重试。这种方式既适用于针对单一路由的限制,也适用于跨所有路由的全局限制。

如何在令牌桶与漏桶之间做选择

在速率限制设计中,最常见的是两种算法,它们对流量的整形方式各不相同。下表给出了对比。

算法突发流量处理能力最佳使用场景
令牌桶允许在桶容量范围内出现可控突发面向开发者的 API,允许偶发突发流量
漏桶严格控制输出速率,不允许突发需要平滑、均匀流量的脆弱下游系统

令牌桶的参数包括令牌填充速率和桶容量。填充速率表示每秒新增多少令牌,桶容量表示桶中最多可存放多少令牌。你需要根据后端对突发流量的承受能力来选择这些值。

如果客户端需要偶尔突发访问,选择令牌桶更合适;如果下游服务要求稳定、可预测的流量,漏桶更适合。如果流量本身具有突发性,令牌桶或滑动窗口计数器通常效果更好;如果流量较均匀,或者你需要严格限制输出速率,那么漏桶是更优选择。

使用 YAML 配置单路由与全局限制

你可以按路由定义速率限制,也可以在整个网关范围内定义全局限制。按路由的限制只作用于特定接口;全局限制则对网关中的所有路由设置统一上限。Redis 作为共享计数存储,用于在分布式环境中统一执行限制。

下面是一个引用 Redis 的 RequestRateLimiter 过滤器 YAML 配置示例:

spring:
  cloud:
    gateway:
      routes:
      - id: auditflow_service_route
        uri: http://localhost:8080
        predicates:
        - Path=/api/v1/audit/**
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: "#{@ipAddressKeyResolver}"
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 20
            redis-rate-limiter.requestedTokens: 1

replenishRate 用于设置每秒补充多少令牌;burstCapacity 定义可用令牌的最大数量;requestedTokens 表示每个请求会消耗多少令牌。key-resolver 则决定限流器基于哪个标识进行统计,例如 IP 地址或用户 ID。

这段配置将限制应用到 /api/v1/audit/** 路径。你可以将类似过滤器添加到其他路由,并设置不同的值。全局限制则会使用跨所有路由共享的 key。当超出限制时,网关会返回带有 Retry-After 头的 HTTP 429,明确告知客户端何时再次尝试。

断路器模式是速率限制的互补机制,它处理的是服务故障,而不是请求量。速率限制控制进入系统的请求数;断路器则阻止请求继续到达已经故障的依赖服务。两者配合,才能同时防范流量高峰和级联故障。

在 API 网关上实现断路器

现在开始实现断路器模式。它会阻止请求继续发送到故障中的下游服务。你的 API 网关会执行一个入站策略来检查断路器状态,也会执行一个出站策略来更新该状态。这个状态机会在三种状态之间切换:closed、open 和 half-open。它与速率限制形成互补:速率限制在入口处控制请求量;断路器则在调用链中间阻断流量,但前提是某个服务已经发生故障。

失败阈值与冷却时间

断路器依赖多个关键参数。失败阈值定义了连续失败多少次后切换到 open 状态。为了便于快速测试,你可以把该值设为 3;在生产环境中,5 次连续失败通常是一个较平衡的选择。这个设置既能避免因为短暂网络抖动而误判,也能在真正故障出现时及时检测。超时时间,或者说冷却时间,决定断路器在 open 状态停留多久后才尝试恢复。生产环境中 60 秒的冷却时间通常比较合适——足够长,可以让大多数服务提供方故障得到恢复;又足够短,不至于在短暂异常之后长时间阻塞流量。如果恢复尝试失败,retry time period 会重新开始计时。

在断路器配置中,如果阈值设得过低,就容易出现误报。断路器会因正常的小波动而被触发,而这些问题本来会自行恢复;如果阈值设得过高,又会导致检测过慢,系统在受到真实损害后才开始保护。因此,你必须根据各自项目的容错能力来配置这些值。

利用 Half-Open 状态进行健康恢复

当超时计时器到期后,断路器会切换到 half-open 状态。在这个状态下,断路器只允许少量探测请求发送到下游服务,目的是测试问题是否已经解决。如果这些探测请求全部成功,断路器就会回到 closed 状态,系统恢复正常运行,同时失败计数器也会重置;如果其中任意一个探测请求失败,断路器就会立即重新回到 open 状态,并重新启动超时计时器。这种方式可以避免“惊群效应”,否则一个尚未完全恢复的服务可能突然再次收到海量请求。

half-open 的超时配置需要格外谨慎。如果 retry time period 设置得过短,你可能会反复冲击尚未恢复的服务;如果设置得过长,又会无谓地阻塞本可正常通过的流量。测试时的最佳实践是从保守值开始,例如使用滚动窗口统计失败次数,如 60 秒内失败 5 次,再根据实际观察逐步调整。

下表总结了状态机的转换逻辑。

状态转换触发条件相关参数
Closed 到 Open滚动窗口内的失败次数或失败比例超过阈值failureThreshold、requestVolumeThreshold
Open 到 Half-Open超时计时器到期Timeout 或 cooldown period
Half-Open 到 Closed所有探测请求均成功successThreshold
Half-Open 到 Open任意一个探测请求失败successThreshold

断路器与速率限制是协同工作的。速率限制在最前端控制输入流量;断路器则在调用链中切断故障传播。当断路器处于 open 状态时,不要继续重试,否则只会导致重试风暴和更严重的级联故障。

结合两种模式打造高韧性 API

执行顺序与策略优先级

现在你已经拥有两种策略,而它们的执行顺序非常关键。速率限制应先在网关层执行,这样可以在请求占用任何下游资源之前,就先拦截过度活跃的客户端。断路器则应在调用链更靠后的位置运行,用于阻止请求继续发送到已知故障中的服务。

下表说明了每种模式如何为系统韧性做出贡献,以及为什么这样的顺序更合理。

模式主要韧性作用为什么必须与另一种模式配合使用
速率限制按消费者、IP 或 API key 限制请求速率应先于重试执行,这样重试不会消耗限流配额
断路器打开断路以阻止重试风暴重试应发生在断路器内部;如果断路器已打开,继续重试毫无意义
组合顺序速率限制,然后重试,然后断路器确保重试不会耗尽配额,也避免持续冲击故障服务

实际落地时可以遵循一个清晰的顺序:先添加速率限制,因为它是最简单、收益也最大的模式;然后为每个后端连接设置合适的超时时间;接着为关键或高故障风险的后端实现断路器;再为读多写少的接口增加缓存降级;最后借助网关可观测性数据持续监控与迭代优化。

通过可观测性支持渐进式恢复

你无法优化看不见的问题。应持续跟踪每个依赖的重试次数,观察断路器打开事件及其持续时长,统计因限流而被拒绝或延迟的请求数量,测量平均响应时间、P95 和 P99 延迟,监控每个接口及每个构建版本的错误率,对比每分钟请求数与 429 响应数,记录每个服务的断路器打开事件,并在某个特定依赖的重试次数突然上升时发出告警。

开始时应保持保守。设置较低的限制值和较低的失败阈值,先观察系统行为再逐步调整。通过测量峰值连接数、请求量和错误率来建立基线,再把初始阈值设为峰值的两倍,并采用适度的异常检测。持续监控一到两周内的溢出与驱逐情况:如果正常流量下没有发生溢出,可适当收紧阈值;如果合法请求被拒绝,则应适当放宽。在预发布环境中运行压测,以验证系统在模拟过载场景下的保护能力。当流量模式或服务容量发生变化时,应重复这一优化周期。

速率限制控制请求量,断路器处理服务故障。两者结合,才能构建一个高韧性的 API 网关。现在,你已经掌握了这两项关键能力。

阈值应依据压测结果和故障目标来设定,而不是凭感觉猜测。只有内部限制,才能真正定义你的系统究竟能承受多少负载。

从保守值开始,观察可观测性数据,再随着真实流量模式逐步调优。断路器奖励的是耐心,而不是激进。

今天就检查你的网关配置吧。通过 YAML 或代码应用这些模式。下一次流量高峰或服务故障不会等你准备好。趁它到来之前,先把你的请求处理机制建立完善。

常见问题

速率限制应该先于断路器执行吗?

是的。应先在网关层执行速率限制,这样可以在 API 调用占用下游容量之前,先限制高频客户端。断路器则在调用链后续阶段执行。这样的顺序可以防止重试消耗你的限流配额,也能避免故障服务继续承受更多负载。

我该如何为断路器选择失败阈值?

可以从 5 次连续失败和 60 秒冷却时间开始。观察错误率和队列深度:如果感觉检测太慢,就降低阈值;如果正常小波动就会触发断路器,就提高阈值。请通过压测来调优,而不是凭直觉决定。

HTTP 429 对客户端意味着什么?

HTTP 429 表示客户端发送了过多请求。网关会拒绝超出的请求,并返回 Retry-After 头。你应该在文档中说明这个响应头的含义,这样客户端就会选择退避重试,而不是继续冲击你的接口。

我真的需要同时使用这两种模式吗?

需要。速率限制控制入站流量上限;断路器隔离故障依赖。前者决定有多少流量进入系统,后者决定这些流量是否应该继续到达已故障的服务。两者结合,才能同时防止过载和级联故障。