---
title: "限流怎么允许短时突发：用单调时钟实现令牌桶"
date: 2026-11-13
slug: token-bucket-monotonic-clock
canonical: https://kernelranch.com/blog/2026/11/token-bucket-monotonic-clock
markdown_url: https://kernelranch.com/blog/2026/11/token-bucket-monotonic-clock.md
tags: ["rate-limit", "algorithms", "python"]
---一个接口要允许短时连发，同时限制长期平均请求速率，可以给每个限流键维护令牌桶。容量决定最多攒多少个请求，补充速率决定用完后多快恢复。计算补充量时用单调时钟的差值，避免墙上时钟调整改变额度。

## 1. 本机复现

保存为 `bucket_lab.py`，运行 `python bucket_lab.py`。示例容量为 3，补充速率为每秒 2 个令牌。用虚拟时钟推进测试，无需休眠。

```python
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)
```

本地实测输出如下。

```text
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 描述的是按字节计量的双桶三色标记机制，本文借鉴补充与容量规则。
