分布式限流在多个实例之间执行一致的流量预算
说明限流键、令牌桶和分布式计数如何保护共享容量,并分析突发、超发、失败模式、重试与长期配额。
分布式限流在多个实例之间执行一致的流量预算
单进程计数器只能保护一个实例。请求经过负载均衡分散到多个节点后,如果每个节点都独立允许每秒一百次请求,系统总入口可能放大为节点数乘以一百。分布式限流需要定义全局或分区预算、计数窗口、突发容量和超限动作,并在一致性、可用性与延迟之间做明确选择。
HTTP 对超限响应已有标准表达。RFC 6585 定义了 429 Too Many Requests,并允许使用 Retry-After 告知客户端等待时间:https://www.rfc-editor.org/rfc/rfc6585.html。它没有规定计数算法,也不意味着客户端一定会遵守,因此服务端仍需保护下游资源。
预算必须绑定清晰的身份和范围
限流键可以是用户、租户、API 密钥、IP、接口、资源或它们的组合。键过粗会让一个调用者影响无关用户,键过细又可能被轮换标识绕过。身份应来自可信认证上下文,不能直接相信客户端提交的租户头。匿名流量可使用网络标识和行为信号,但共享出口、代理和地址变化会带来误伤。
预算还要说明作用范围。每租户每分钟一千次与每接口每秒五十次解决的问题不同;前者提供公平性,后者保护瞬时容量。实践中常叠加组织总额、用户额度和昂贵操作额度。多层规则发生冲突时,要规定扣减顺序和返回信息,避免一次请求被重复计费。
读写成本不同,不能一律按请求数计量。批量查询、导出和复杂空间运算可使用权重令牌,令牌数由经过验证的参数决定。权重计算本身要设上限,不能让恶意输入在限流前就消耗大量解析资源。
算法决定突发与边界行为
固定窗口计数实现简单,但窗口交界处可能允许两倍突发。滑动日志精确记录每次请求,存储和清理成本较高;滑动窗口计数用相邻窗口加权近似,降低成本但不是完全精确。令牌桶按稳定速率补充令牌,并允许不超过桶容量的突发,适合需要吸收短时峰值的接口。漏桶更强调固定输出速率,常用于平滑队列。
选择算法前应先描述业务语义:是否允许积累额度、峰值持续多久、时钟误差多大、超限请求是否排队。把“每秒一百次”写入配置而不说明突发和窗口,客户端与服务端会得到不同理解。
集中计数存储能提供较统一判断,但每个请求增加网络往返,并形成新依赖。节点本地预取一批令牌可以降低延迟和存储压力,却会造成短时超发;预取批次越大,故障时未归还额度越多。设计应量化最大超发,而不是笼统声称“强一致”或“最终一致”。
故障时要优先保护核心资源
计数服务不可用时,系统必须预先选择失败开放或失败关闭。公开低成本读取可能短时采用保守本地额度,避免全站不可用;支付、批量计算或第三方计费接口更适合失败关闭。按接口统一处理通常不够,应根据下游容量和业务风险分级。
时钟不一致会影响窗口和令牌补充。依赖数据库或缓存服务器时间可减少节点差异,但仍要处理网络延迟和主从切换。使用租约或过期键时,应验证故障转移后时间语义。计数更新需要原子操作,读取后再写入的两步逻辑会在并发下放行过多请求。
重试是常见放大器。客户端收到 429 后若立即同步重试,会制造周期性尖峰;应提供合理的 Retry-After,客户端使用带抖动的退避。HTTP 语义与重试边界可结合 RFC 9110 查阅:https://www.rfc-editor.org/rfc/rfc9110.html。服务端也应限制排队长度,避免把被拒流量转成内存压力。
观测与验证要覆盖公平性
指标至少包括允许、拒绝、排队和降级请求数,按规则、租户和接口聚合;同时观察计数存储延迟、错误、热点键、预取额度与估算超发。拒绝率升高可能是攻击、客户端错误,也可能是容量配置落后,不能只以减少 429 为优化目标。
测试应包含窗口边界、并发竞争、多节点扩缩、计数服务主从切换、网络分区、节点时钟偏移和客户端重试。对预取或近似算法,要计算最坏情况下的全局放行量。容量压测应穿过真实网关、认证和下游,不只测试计数器函数。
配置变更需要版本、审计和灰度。突然降低限额会让正常批处理失败,突然提高则可能击穿数据库。可以先以只记录模式评估影响,再按租户逐步执行。紧急豁免必须有过期时间和责任人,避免永久旁路。分布式限流的价值不在于精确拒绝每一个多余请求,而在于以可解释、可验证的预算稳定保护共享系统。
配额与限流应当分开结算
限流控制短时间内的速率,配额控制一天、一个账期或合同周期的累计用量。把月度配额直接实现成一个超长令牌桶,会使退款、迟到计费和账单对账非常困难。短期限流可以使用高性能近似计数,长期配额则需要可审计账本、幂等消费记录和争议处理。两者都可能拒绝请求,但返回原因、恢复时间和客户沟通不同,接口应提供稳定的错误码和剩余额度信息。