API 网关专栏 · 第 52 章

分布式 API 网关限流:本地计数器、Redis 与精度权衡

2026年09月09日
分布式 API 网关限流:本地计数器、Redis 与精度权衡

如果策略只是每个网关实例的快速保护线,并且可以接受集群总量存在偏差,可以使用节点本地计数器。如果多个网关实例必须共同执行一个租户、凭证或路由预算,则使用 Redis 共享计数器。共享设计能改善协同,却也引入了网络依赖,因此必须明确 Redis 变慢或不可用时如何处理请求。

不存在适用于所有场景且绝对精确的分布式限流器。负载均衡偏斜、重试、计数窗口、时钟行为、弹性扩缩容和依赖故障都会影响客户端的实际体验。应先定义业务不变量,再选择能够保护它的计数方式和故障策略。

核心要点

  • 本地计数器延迟低、故障隔离好,但每个网关实例拥有独立状态。
  • Redis 计数器可以协调集群,但 Redis 的延迟和可用性会进入请求路径。
  • 稳定且经过认证的身份通常比源 IP 更适合作为限流键。
  • 应按路由决定故障时放行还是拒绝;allow_degradation 是可用性与安全决策,而不是普通调优开关。
  • 上线前应测试扩缩容、负载均衡偏斜、计数存储故障和热点租户。

先定义真正需要的限制

“每秒 100 个请求”并不完整,还应明确以下维度:

维度决策示例
主体按 API 凭证、消费者、租户、路由或 IP
范围按网关实例、区域或全局集群
算法漏桶、令牌桶、固定窗口或滑动窗口
突发短时突发是延迟、接受还是拒绝
响应状态码、响应体和重试指引
故障计数存储失败时拒绝、绕过或使用本地后备
目标公平性、成本保护、滥用控制或上游容量保护

登录接口可能需要保守且感知身份的滥用限制。公共目录读取可以使用本地保护线维持各网关健康。付费推理接口则可能需要共享租户配额,否则每增加一个网关节点都会放大支出上限。

本地与 Redis 计数器解决不同问题

Apache APISIX 的 limit-req 插件采用漏桶限流,并记录了 local、redis 和 redis-cluster 三种策略。

本地策略

使用 policy: local 时,每个 APISIX 实例独立维护计数器。这样无需远程查询,也不会因为 Redis 故障而使限流器失效,适合保护单实例,以及流量分区可预测的部署。

其代价是作用范围。如果一个客户端被平均分配到四个均配置为每秒 100 个请求的实例,集群可能接受约四倍于单实例配置的请求。实际结果受流量分布和突发影响,并不是固定倍数。扩缩容也会改变总准入量,除非重新计算单实例速率。

Redis 策略

使用 policy: redis 时,多个 APISIX 实例共用计数状态。这样,同一个键可以在整个集群内执行一项策略,新增网关节点也不会自然放大租户额度。

与此同时,Redis 进入了策略路径。网络延迟、连接上限、认证、TLS、集群故障转移和热点键负载都必须纳入设计。共享计数可以改善一致性,但无法保证所有竞争和故障条件下都具有数学意义上的绝对精确。

选择稳定的限流键

限流键决定哪些请求共享同一个桶。如果策略代表使用权益,应优先选择已认证的 API 消费者或租户标识。只有在可信代理链已经恢复真实地址,而且能够接受共享 NAT、IPv6 隐私地址和移动网络造成的误差时,才使用源 IP。

不要直接采用由攻击者任意指定且无界的键。原始查询参数会制造高基数计数状态,也允许调用方通过不断换值获得新桶。组合键应规范化,并按环境和路由设置命名空间,同时定义过期行为。

配置 APISIX 共享限流

以下 Apache APISIX 3.18 示例路由按已认证消费者名称实施共享请求速率。请为实际环境替换端点、凭证和限制值,并通过部署环境的密钥流程存储凭证,切勿提交真实密钥。

1curl "http://127.0.0.1:9180/apisix/admin/routes/report-export" \
2  -X PUT \
3  -H "X-API-KEY: ${admin_key}" \
4  -d '{
5    "uri": "/v1/reports/export",
6    "plugins": {
7      "key-auth": {},
8      "limit-req": {
9        "rate": 5,
10        "burst": 2,
11        "key_type": "var",
12        "key": "consumer_name",
13        "policy": "redis",
14        "redis_host": "redis.internal",
15        "redis_port": 6379,
16        "redis_password": "<managed-secret>",
17        "redis_database": 2,
18        "redis_timeout": 1000,
19        "allow_degradation": false,
20        "rejected_code": 429,
21        "rejected_msg": "Rate limit exceeded"
22      }
23    },
24    "upstream": {
25      "type": "roundrobin",
26      "nodes": {"reports.internal:8080": 1}
27    }
28  }'

该示例用于说明可审查的配置,并不表示其中数值适合生产环境。limit-req 参考文档才是确认所用 APISIX 版本支持哪些字段的依据。如果 Redis 跨越不可信网络,应按照文档选项和所用发行版配置加密与证书验证,而不是依赖默认值。

明确决定故障行为

APISIX 文档说明 allow_degradation: false 是默认值。设为 true 后,如果限流依赖发生错误,请求可以绕过插件。这可能维持可用性,却也会在策略系统受损时撤销原本的保护。

如果策略是财务、滥用或合同的硬边界,而且不受控执行比拒绝更危险,应选择故障关闭。对于低风险读取路径,如果限流只提供建议性保护,并且拒绝全部合法流量的伤害更大,可以考虑故障开放。本地应急后备有时有价值,但不要假定插件会自动提供你设想的后备语义;应针对所用版本验证实现。

限流器错误与普通 429 响应应分别告警。计数存储不可用属于基础设施事故,而正常拒绝属于策略结果。

安全运维共享计数器

  • 将计数流量与无关 Redis 工作负载隔离,或明确设置容量和淘汰策略。
  • 按环境、策略、路由和主体设置键命名空间,避免意外冲突。
  • 保护凭证,需要时启用传输安全,并限制网络访问。
  • 监控命令延迟、超时、连接饱和、错误、内存和热点键。
  • 规划 Redis 维护和故障转移,并在两种场景下演练请求行为。
  • 对策略变更进行版本管理和渐进发布,尤其是修改键或作用范围时。
  • 保持响应语义稳定,让客户端知道何时应减速。

不要在每次计数失败时都进行无预算重试。对已退化的 Redis 反复重试会放大负载并增加网关延迟。

上线前验证

APISIX 限流入门教程演示了插件的基本行为。实际测试还应覆盖生产拓扑:

  1. 让一个身份通过单实例,确认稳态速率和突发行为。
  2. 将同一身份分散到所有网关,对比本地与 Redis 策略。
  3. 在流量持续时增加和移除实例。
  4. 将流量集中到一个节点,暴露负载均衡偏斜。
  5. 停止或延迟 Redis,验证选定的故障开放或关闭结果。
  6. 同时驱动大量身份和一个热点租户,观察延迟与计数存储饱和情况。
  7. 确认被拒请求没有到达受保护的上游。
  8. 确认仪表板能够区分 429 策略结果和限流器故障。

决策检查清单

  • 该限制是单节点保护线,还是集群级业务不变量?
  • 哪个已认证或可信属性拥有计数桶?
  • 合法客户端需要怎样的突发行为?
  • 扩缩容会如何改变本地策略的有效速率?
  • 共享存储能否满足延迟与可用性预算?
  • 计数器不可用时,该路由应故障开放还是关闭?
  • 密钥、TLS、键命名空间和存储容量是否得到管理?
  • 多节点、偏斜、故障转移和热点键测试是否通过?

总结

本地限流通常是保护各 API 网关实例最简单的方式。如果一个预算必须跟随主体跨越整个集群,则适合使用 Redis 限流。协同的代价是新增依赖和更重要的故障策略。应明确主体、范围、突发、响应和故障行为,并测试完整拓扑,而不是只验证单个网关。

常见问题

Redis 能让分布式限流绝对精确吗?

它提供共享状态并改善集群级协同,但算法语义、网络故障、竞争、重试和拓扑仍会影响观察结果。应把它视为明确的一致性设计,而不是普遍的绝对精确保证。

可以用全局限制除以网关节点数吗?

只有在流量分配可预测时才能作为近似值。弹性扩缩容和负载均衡偏斜会改变有效总量及各客户端体验。

限流应该故障开放吗?

取决于受保护的操作。如果不受控执行比拒绝更危险,针对滥用或成本硬边界应故障关闭;只有在明确优先可用性且残余风险可接受时才故障开放。

后续步骤

使用 API 网关并发控制同时限制在途工作与到达速率。如果需要托管控制面和企业级策略工作流,可以进一步了解 API7 企业版。

获取方案