限流怎么允许短时突发:用单调时钟实现令牌桶
一个接口要允许短时连发,同时限制长期平均请求速率,可以给每个限流键维护令牌桶。容量决定最多攒多少个请求,补充速率决定用完后多快恢复。计算补充量时用单调时钟的差值,避免墙上时钟调整改变额度。
1. 本机复现
保存为 bucket_lab.py,运行 python bucket_lab.py。示例容量为 3,补充速率为每秒 2 个令牌。用虚拟时钟推进测试,无需休眠。
from time import monotonic_ns
SECOND = 1_000_000_000
class Bucket:
def __init__(self, capacity, rate, clock=monotonic_ns):
if capacity <= 0 or rate <= 0:
raise ValueError("capacity and rate must be positive")
self.ceiling = capacity * SECOND
self.credit = self.ceiling
self.rate = rate
self.clock = clock
self.previous = clock()
def take(self):
now = self.clock()
self.credit = min(
self.ceiling, self.credit + (now - self.previous) * self.rate
)
self.previous = now
if self.credit < SECOND:
return False
self.credit -= SECOND
return True
def requests(count):
return "".join("Y" if bucket.take() else "N" for _ in range(count))
now = 0
bucket = Bucket(capacity=3, rate=2, clock=lambda: now)
initial = requests(4)
assert initial == "YYYN"
print("0 ms:", initial)
now = 499_000_000
before = requests(1)
assert before == "N"
print("499 ms:", before)
now = 500_000_000
after = requests(2)
assert after == "YN"
print("500 ms:", after)
now = 10 * SECOND
idle = requests(4)
assert idle == "YYYN"
print("10 s:", idle)
本地实测输出如下。
0 ms: YYYN
499 ms: N
500 ms: YN
10 s: YYYN
2. 换算恢复时间并确定共享范围
代码用整数表示额度,每个请求消耗 1_000_000_000 个额度单位,每经过 1 纳秒补入 rate 个单位。参数约定为正整数。499 毫秒只有 0.998 个令牌,500 毫秒刚够 1 个。被拒绝的请求也更新补充进度,因此上一小段积累不会重复计算。
同一进程如果有多个线程共享桶,读取时间、更新余额和扣减需要在同一个临界区内完成。多个进程各自持桶时,实际总速率与桶的数量有关。若希望全局共享同一个额度,需要在共享存储中原子更新状态,并为时间来源制定一致的规则。单调时钟读数的参考点未定义,适合进程内计算差值。
小结
用容量控制突发,用补充速率控制恢复。评审配置时先明确额度由哪些请求和实例共享。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Python time.monotonic_ns | https://docs.python.org/3.11/library/time.html#time.monotonic_ns | 单调时钟与纳秒整数 |
| RFC 2697 第 3 节 | https://www.rfc-editor.org/rfc/rfc2697.html#section-3 | 网络流量计量中令牌按速率补充、按容量封顶的实例 |
核验说明
- Windows、Python 3.11.9 使用合成时钟验证突发、499/500 毫秒边界和空闲后容量封顶。示例是单线程请求计数桶。RFC 2697 描述的是按字节计量的双桶三色标记机制,本文借鉴补充与容量规则。