← 博客
10 分钟阅读
|
2026年9月25日

面向 API 的原子化 Redis Lua 限流:开发者策略指南

面向开发者的 Redis 就绪 API 限流:可选滑动窗口、令牌桶或漏桶算法;使用原子化 Redis Lua 脚本并返回 RateLimit 响应头。

章节
C
Chaingateway Team
区块链专家

原子请求控制的等轴测插图

对于大多数公共 API,滑动窗口计数器是正确的默认选择:它以每个客户端恒定的内存提供近乎精确的准确性。当你需要允许受控突发时,请使用令牌桶;当脆弱的下游服务要求严格且稳定的流出速率时,请使用漏桶。根据内存预算、突发容忍度以及下游系统的脆弱程度进行选择,并使用由 RateLimit 标头支持的原子 Redis 和 Lua 脚本实施执行逻辑。


TL;DR:

  • 滑动窗口计数器平衡了准确性和内存效率,使其适用于高流量的通用公共 API。
  • 使用 Redis Lua 脚本原子性地实现速率限制器,可防止竞争条件并保持多个应用程序实例间的一致性。
  • 将补充速率设置为略高于正常 P99 流量,并将桶容量设置为可处理 5 到 10 秒的突发流量而不产生误报。
  • 通过标准化标头告知客户端其速率限制,并提供清晰的 Retry-After 响应,以帮助它们有效管理重试。
  • 在边缘层(如 NGINX)和应用层分层实施限制,并根据内存限制、突发容忍度和下游系统脆弱性选择算法。

目录

主要速率限制算法对比

五种算法几乎涵盖了所有生产场景;每种都在内存成本、突发容忍度和准确性之间的权衡中处于不同位置。

  • 固定窗口:每个时间槽一个计数器,运行成本最低,但允许在窗口边界附近发生突发。
  • 滑动窗口日志:存储每个请求的时间戳,以内存随请求量增长为代价提供精确计数。
  • 滑动窗口计数器:将当前窗口和前一个窗口的计数混合为加权估算值,保持每个客户端的内存恒定。
  • 令牌桶:跟踪令牌数量和补充时间戳,允许客户端在短时间内消耗累积的容量。
  • 漏桶:无论请求如何到达,都以固定的输出速率处理请求,为脆弱的下游平滑流量。

固定窗口适用于低风险的内部限制,滑动窗口日志适用于低流量、高风险的端点(如登录),滑动窗口计数器适用于通用公共 API,令牌桶适用于需要突发缓冲的面向开发者的 API,漏桶适用于速率敏感型后端前的队列。

各算法的实现要点

每种算法都需要特定的状态结构,并有其自身的运行时开销,因此选择算法本质上是在选择数据结构。

五种 Redis 限流算法对比

固定窗口按客户端 ID 和时间桶存储单个计数器,每次请求时递增,桶滚动时重置。它易于理解,但客户端可在一个窗口末尾发送满额度请求,又在下一窗口开头发送满额度请求,在短时间内使有效速率翻倍。这使其适用于粗粒度、低风险的限制,但用于安全敏感场景则有风险。

滑动窗口日志为每个客户端维护一个请求时间戳的有序集合,每次检查时修剪窗口以外的旧数据。它是精确的,因为它统计真实请求而非估算,但内存随请求量增长,对高流量客户端成本较高。

滑动窗口计数器仅存储两个计数器(当前窗口和前一窗口各一个),并根据当前窗口已过的比例计算加权估算值,从而避免了上述开销。Cloudflare 的边缘限流即采用此方法,将 PoP 级控制与中心化计数器结合,在保持内存平稳的同时支撑大规模流量。

令牌桶为每个客户端存储令牌数和上次填充时间戳。每次请求时,计算自上次检查以来获得的令牌数,将总数上限限制为桶容量,若有可用令牌则扣减一个。填充和消耗必须原子执行,否则两个并发请求可能读到相同的令牌数并均成功,而实际上只应允许一个。

漏桶有两种变体:一种是监管型,直接丢弃超过流出速率的请求;另一种是整形型,将请求排队等待后续处理。当端点前接的是无法吸收流量尖峰的数据库或第三方服务时,应选用它。

专业提示: 将填充和消耗逻辑封装在单个 Lua 脚本中,而非分离的 GET 和 SET 调用;跨两次往返的读取-写入序列正是原子脚本要防止的竞态条件。

构建跨实例依然有效的限流器

一个在单进程下工作但在并发下失效的限流器,比没有限流器更糟糕,因为它给人虚假的安全感。

  1. 将计数器存储在 Redis 中,以便每个应用实例读写相同的状态,而不是各自漂移。
  2. 使用 Redis Lua EVAL 脚本将读取、补充和消耗步骤合并为一个原子操作,正如 Redis 官方的限流教程 所推荐的,因为在高并发下 MULTI/EXEC 和乐观锁都会留下漏洞。
  3. 对于大流量服务,在每个实例或每个接入点本地聚合计数,然后再同步到中央存储,而不是在每个请求上都访问 Redis。
  4. 在网关层叠加一个漏桶算法,例如在 NGINX 中,作为抵御滥用流量的外层防线,并在应用层为合法但突发的客户端保留更精细的按键限制。
  5. 在事故发生前,针对每个端点预先决定故障开启还是故障关闭:在读多写少的公共端点上故障开启,这样 Redis 故障不会拖垮整个 API;在认证或支付端点上故障关闭,因为放行无限流量的风险更大。

专业提示: 将每个故障开启事件单独记录,区别于正常流量;一个悄悄禁用了你限流规则的 Redis 故障,正是那种只会在事后复盘中才暴露的故障。

为你的端点选择合适的算法

在编写任何代码前,先从四个维度进行评估:每个客户端可承受的内存开销、突发流量是合理还是威胁、下游系统的脆弱程度,以及是否必须精确计数还是估算即可。

  • 公开开发者 API:滑动窗口计数器或令牌桶,因为两者都能容忍合理的突发,且无需精确计数的开销。
  • 支付和认证端点:滑动窗口日志以保证精确性,或采用默认故障关闭策略,宁可拒绝请求也不放行可疑流量。
  • 下游受限流程:漏桶算法,确保输出速率永不超过脆弱的下游系统所能承受的上限。
  • 大流量、低风险内部流量:固定窗口,用边界突发换取最简单的实现。

如果你无法回答 Redis 不可达时会发生什么,那么无论选了哪种算法,设计都还没完成。

通过响应头向客户端传达限制信息

服务端应在客户端开始猜测前告知其当前状态。一份新兴的 IETF 草案 定义了 RateLimit 和 RateLimit-Policy 响应头,其中 RateLimit-Limit 和 RateLimit-Reset 为必选,RateLimit-Remaining 推荐但可选。许多主流服务商仍在使用较早的厂商前缀 X-RateLimit-* 响应头,它们早于该草案,因此在过渡期同时支持两者是合理的。

  • 在每个响应中返回 RateLimit-Limit、RateLimit-Remaining 和 RateLimit-Reset,而不仅是在拒绝请求时。
  • 每当以 429 状态码拒绝请求时,发送 Retry-After 响应头。
  • 将这些响应头视为提示而非保证,因为负载可能在响应头签发和下一个请求之间发生变化。
  • 客户端应使用带完全抖动的指数退避,而非固定延迟,以分散重试请求,避免形成同步重试风暴。

生产环境参考报告 Cloudflare 的滑动窗口计数器在极大请求量下以极低的错误率运行,证明了基于估算的方法在大规模场景下依然站得住脚,且不会牺牲有意义的准确性。

设置令牌填充速率、桶容量和告警

从现有的指标管道中获取每个客户端的 p95 和 p99 每秒请求数,然后将填充速率设置在持续 p99 略高的位置,以便正常使用永远不会触发限流器。将桶容量设定为可吸收约 5 到 10 秒的预期 p99 突发流量,这足以覆盖客户端重试批处理作业的情况,而不会惩罚其他所有人。

  • 将 429 速率和限流比例作为总请求数的占比来监控,而不仅仅是原始计数。
  • 分别跟踪被限流和未被限流流量的请求延迟,因为前者的激增往往预示着后者的激增。
  • 对与限流器相关的 Redis 命令失败或超时设置告警,因为那里的静默失败会使整个系统失效。
  • 先向一小部分流量推出限制变更,待 429 速率和延迟都表现稳定后再逐步扩大范围。

专业提示: 当你收紧限制时,在变更上线前向 API 消费者宣布该变更及新的响应头。一个毫无上下文的突发 429 错误,产生的支持工单会比它原本要阻止的滥用行为更多。

这些模式在生产环境中的体现

大多数生产技术栈将执行拆分为两层,而不是依赖单一层面。

  • 边缘层:NGINX 漏桶配置在流量到达应用服务器之前,吸收滥用流量和明显的爬虫抓取。
  • 应用层:实现令牌桶或滑动窗口计数器的 Redis Lua 脚本执行按键限制,通常在只读端点上采用“故障开放”策略,以防缓存中断导致 API 整体瘫痪。
  • 公开开发者 API:滑动窗口计数器或令牌桶,根据客户端所在的套餐层级进行调优。
  • 认证端点:严格、低容量的限制,通常采用“故障关闭”策略,因为在此处放行过量流量属于安全风险,而非单纯的不便。
  • Webhook 处理器:漏桶或基于队列的限流器,因为发送服务的重试需要被平滑处理,而非直接拒绝。

公平性、滥用防范与限流伦理

限流既是一种公平机制,也是一种技术手段:它决定在需求超过容量时为哪些请求提供服务,而这一决策影响着真实的用户和真实的业务。限制设置过于激进会在流量高峰期间将合法客户拒之门外,而设置过于宽松则会让少数滥用客户降低其他所有人的服务质量。

在 API 文档中公开你的限制及其背后的理由,因为未公开的限流会被视为武断,并会削弱构建在你的 API 之上的开发者的信任。在相似客户端间一致地应用限制,而不是暗中偏袒某些账户,并在服务条款中明确界定何为滥用流量(如凭证填充或爬虫抓取),以及何为付费客户的正常高流量使用。

当你对客户端进行限流时,响应本身在伦理和技术层面都至关重要。一个带有清晰 Header 和 Retry-After 值的 429 响应尊重客户端的时间,并让其系统能够优雅地恢复;而静默丢弃或模糊的错误则迫使他们去猜测。对于多租户平台,应按租户隔离限制,这样某个客户的流量激增(无论是合法的还是恶意的)就无法耗尽共享同一基础设施的其他租户的配额。这种隔离往往是轻微事故与背叛无辜付费客户信任之间的分水岭。

带有重试路径的隔离租户通道

为什么合理的默认值能救你于未来

你选择的算法不如选择一个并诚实地监控它重要。滑动窗口计数器和令牌桶以最小的运维开销覆盖了大多数情况,但如果没有退避或 Header 感知的天真客户端重试仍会导致中断。让产品、SDK 和基础设施团队在客户以惨痛教训发现问题之前,就限制问题保持沟通。

— Bitblade

去哪里阅读更多关于限流标准的内容

从 IETF RateLimit Header 草案开始了解新兴标准,然后阅读 Redis 自己的限流器教程获取实现代码。

来源

常见问题

有哪些有效的限流策略?

最有效的策略将适合流量模式的算法(如用于通用 API 的滑动窗口计数器或用于突发性客户端的令牌桶)与原子执行相结合,这样并发请求就无法绕过限制。正如 Cloudflare 的方法所示,将边缘层控制与应用层按键限制相结合,增加了第二层保护。

如何实现 API 限流?

将计数器或令牌状态存储在 Redis 等共享存储中,并将读取、补充和消耗步骤封装在单个原子 Lua 脚本中以避免竞争条件,这一模式在 Redis 的限流器教程中有详细说明。在每个响应上返回清晰的 RateLimit Header,在拒绝时返回 Retry-After Header,以便客户端知道如何表现。

你将如何为 API 设计限流?

首先检查内存预算、突发容忍度以及下游系统的脆弱程度,然后将这些约束与算法匹配:公共 API 使用滑动窗口计数器或令牌桶,敏感端点使用滑动窗口日志或故障关闭默认值。根据测量的 p99 流量调整补充速率,并将桶容量调整为可吸收几秒钟的预期突发。

什么是 API 限流?

API 速率限制是指在给定时间段内限制客户端可发出的请求数量,以保护服务免受过载并保持各客户端的使用公平。服务器通常通过响应头传达限制和剩余配额,这种方法已在 IETF RateLimit 头部草案中正式化。

推荐

准备好自己动手构建了吗? 获取你的 API key — 7 天试用,无需信用卡 — 或查看 区块链 API 获取完整的 endpoint 参考。

C
Chaingateway Team
区块链专家

Chaingateway 团队致力于为全球开发者简化区块链集成。