令牌桶是一种允许突发流量的限流算法,系统以固定速率向桶添加令牌,请求需获取令牌才能执行;python可用threading实现单机版,高并发分布式场景须用redis+lua保证原子性。

什么是令牌桶限流
令牌桶是一种经典的限流算法,核心思想是系统以固定速率向桶中添加令牌,每次请求需要先获取一个令牌才能执行。桶有最大容量,满了就不再添加;没令牌时请求被拒绝或排队等待。相比计数器(简单粗暴)和漏桶(恒定流出),令牌桶允许一定突发流量,更贴合真实业务场景。
Python中用threading实现简易令牌桶
适合单进程、非高并发场景(如内部工具接口、管理后台API)。关键点:用锁保护共享状态,避免多线程竞争。
- 维护两个变量:当前令牌数
self._tokens和上次填充时间self._last_fill - 每次请求前尝试补充令牌:根据间隔时间计算应新增数量,但不超过桶容量
- 获取令牌失败则返回False:可据此直接返回429 Too Many Requests
示例代码片段:
import time import threading <p>class TokenBucket: def <strong>init</strong>(self, capacity: int, fill_rate: float): self.capacity = capacity self.fill_rate = fill_rate # tokens per second self._tokens = capacity self._last_fill = time.time() self._lock = threading.Lock()</p><pre class='brush:python;toolbar:false;'>def _fill(self): now = time.time() elapsed = now - self._last_fill new_tokens = elapsed * self.fill_rate self._tokens = min(self.capacity, self._tokens + new_tokens) self._last_fill = now def consume(self, tokens: int = 1) -> bool: with self._lock: self._fill() if self._tokens >= tokens: self._tokens -= tokens return True return False
结合flask/fastapi做接口限流
在Web框架中使用时,推荐封装为装饰器或中间件,避免每个路由重复写逻辑。
立即学习“Python免费学习笔记(深入)”;
- Flask示例(装饰器):创建
@rate_limit(limit=10, per=60),内部调用上面的TokenBucket实例(注意:多worker需改用Redis后端) - FastAPI示例(依赖注入):定义
Depends(get_bucket),把桶实例注入到路径操作函数中 - 关键提醒:单机多进程(如gunicorn多个worker)下,内存级桶不共享,必须用Redis等外部存储同步状态
生产环境建议用Redis+Lua实现原子性
高并发、分布式服务必须保证“检查+扣减”是原子操作,否则会出现超卖(超限)。Redis的单线程特性和Lua脚本支持完美解决这个问题。
- 用一个key存当前令牌数和最后更新时间(如哈希结构或拼接字符串)
- Lua脚本内完成:读取旧值 → 计算新令牌数 → 判断是否足够 → 扣减并更新 → 返回结果
- Python端只需调用
redis.eval(lua_script, ...),一行拿到是否放行
这样既避免网络往返开销,又杜绝竞态条件,是线上首选方案。