3. 令牌桶算法详解
令牌桶算法(Token Bucket)是金融 API 限流中最常用的算法之一。它允许一定程度的突发流量,同时保证长期平均速率不超过设定阈值,非常适合处理交易、行情订阅等具有突发特征的金融请求。
3.1 工作机制
令牌桶的核心思想是:系统以恒定速率向一个“桶”中放入令牌,每个请求必须从桶中获取一个令牌才能被处理。如果桶中没有令牌,请求将被拒绝或等待。
关键特性: 允许突发流量——桶中最多可累积 容量(burst) 个令牌,当突发请求到来时,可以一次性消耗所有累积令牌,实现短时间内的流量爆发。
3.2 参数配置
令牌桶算法有两个核心参数,直接决定限流效果:
| 参数 | 符号 | 含义 | 金融场景示例 |
|---|---|---|---|
| 速率(Rate) | r | 令牌生成速度,单位:个/秒 | r = 100,表示每秒最多处理 100 笔交易 |
| 容量(Capacity) | b | 桶的最大令牌数,决定突发能力 | b = 200,允许瞬间处理 200 笔积压请求 |
配置建议: 金融 API 中,建议 b ≥ 2 × r,以应对行情波动时的突发查询。例如,若速率设为 500 QPS,容量可设为 1000~1500,避免瞬时高峰被误杀。
3.3 Python 代码实现
以下是一个完整的令牌桶限流器实现,包含时间戳驱动的令牌补充逻辑:
import time
import threading
class TokenBucket:
"""令牌桶限流器"""
def __init__(self, rate: float, capacity: int):
"""
:param rate: 令牌生成速率(个/秒)
:param capacity: 桶容量(最大令牌数)
"""
self.rate = rate
self.capacity = capacity
self.tokens = capacity # 初始满桶
self.last_refill = time.time() # 上次补充时间
self.lock = threading.Lock() # 线程安全
def _refill(self):
"""补充令牌"""
now = time.time()
elapsed = now - self.last_refill
new_tokens = elapsed * self.rate
self.tokens = min(self.capacity, self.tokens + new_tokens)
self.last_refill = now
def consume(self, tokens: int = 1) -> bool:
"""
消费令牌
:return: True 表示允许请求,False 表示限流
"""
with self.lock:
self._refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
# 使用示例
if __name__ == "__main__":
# 创建限流器:每秒 10 个令牌,容量 20
bucket = TokenBucket(rate=10, capacity=20)
# 模拟 30 个并发请求
for i in range(30):
allowed = bucket.consume()
status = "✅ 通过" if allowed else "❌ 限流"
print(f"请求 {i+1:2d}: {status} (剩余令牌: {bucket.tokens:.1f})")
time.sleep(0.05) # 模拟请求间隔
3.4 可视化分析
通过模拟请求并记录令牌变化,可以直观观察令牌桶的突发吸收能力:
可视化解读: 上图展示了令牌桶在稳定状态下的表现。前 3 秒请求平稳,令牌始终维持在容量上限(20)。第 3 秒出现突发请求,令牌被快速消耗至接近 0,但桶的容量允许这批突发通过。随后系统恢复,令牌按速率重新累积,体现了“允许突发、平滑长期”的核心特性。
3.5 金融场景适用性分析
| 场景 | 推荐配置 | 原因 |
|---|---|---|
| 行情订阅 API | r=1000, b=3000 | 行情波动大,需要较大突发能力 |
| 交易下单 API | r=50, b=100 | 交易频率低但需防刷单 |
| 账户查询 API | r=200, b=400 | 查询请求均匀,突发需求较小 |
注意事项: 令牌桶算法不保证请求的公平性——如果桶中令牌不足,后到的请求可能被丢弃,而先到的请求可能因为等待而超时。在金融场景中,建议结合 请求优先级队列 或 加权令牌分配 来优化。