令牌桶算法是当前接口限流最实用的方案,它允许突发流量同时平滑整体速率,配合Redis或本地内存即可落地。
很多团队在接口设计初期没有考虑限流,直到压测时发现某个调用方把服务拖垮,限流算法各有取舍,令牌桶凭借“允许一定突发”的特性,成为大多数业务系统的首选,下面从原理、实现、参数调节到对比,完整拆解这套方案。
令牌桶算法接口限流原理与漏桶有何区别
令牌桶的核心思路是:系统以固定速率往桶里放令牌,每个请求进来必须取走一枚令牌才能通过,桶有容量上限,放满了就不再增加,所以可以容忍瞬间的突发流量。
拿生活中的例子比喻:景区入口每分钟放行30人,但门口能同时容纳100人排队等待,正常时段游客随到随进,节假日瞬间来了一拨人,只要排队区域没满,他们都可以马上进入,而不是被强制限速到每分钟30人,这就是令牌桶和漏桶最本质的差别。
漏桶算法则完全相反,它把请求比作水流进一个固定大小的桶,桶底匀速漏水,无论上游流量多猛,漏桶出口速率永远恒定,毫无突发能力,适合需要严格平稳输出的场景,比如数据库写入、消息队列消费。
行业共识认为,网关层面最常见的限流实现就是令牌桶,因为API接口天然需要应对瞬时峰值,比如秒杀开场、热点新闻推送、定时任务集中回调,把这些场景全部削平,反而会影响用户体验。
令牌桶的基本工作流程
一个标准令牌桶包含三个要素:桶容量、填充速率、当前令牌数,每次请求到达时执行以下步骤:
- 计算自上次填充到现在应该补充多少令牌,公式为
(当前时间 - 上次补充时间) 填充速率。 - 将补充后的令牌数限制在桶容量以内,多余的直接丢弃。
- 如果当前令牌数大于等于1,则取出一个令牌,放行请求。
- 如果没有令牌,请求被拒绝或进入等待队列。
注意,补令牌的时机有两种实现方式,一种是每次请求来时计算增量,称为“懒更新”,适合高并发场景,不需要后台定时任务,另一种是用独立线程每秒补充一次,逻辑简单但会引入额外的调度开销。
伪代码视角看核心逻辑
class TokenBucket {
double tokens; // 当前令牌数
double capacity; // 桶容量
double rate; // 每秒补充速率
long lastRefillTime; // 上次补充时间戳

synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 懒更新:先补偿令牌
tokens = Math.min(capacity, tokens + (now - lastRefillTime) / 1000.0 rate);
lastRefillTime = now;
if (tokens < 1) {
return false;
}
tokens -= 1;
return true;
}
}
这段逻辑可以直接嵌入业务代码,也可以放到Gateway过滤器或Nginx Lua脚本里,关键点是加锁保证并发安全,如果使用Redis原子操作,则无需显式加锁。
接口限流方案对比:令牌桶、漏桶与滑动窗口谁更实用
很多开发者纠结限流方案选型,网上资料往往把各种算法的优劣讲得过于抽象,这里直接用实际场景做对比。
效果对比表
| 维度 | 令牌桶 | 漏桶 | 滑动窗口 |
|---|---|---|---|
| 突发流量 | 允许,但不超过桶容量 | 完全不允许 | 允许微弱波动 |
| 实现难度 | 中等 | 简单 | 简单 |
| 内存占用 | 常量 | 常量 | 需要存储窗口内的时间戳列表 |
| 适用场景 | 业务接口、秒杀、API网关 | 严格匀速的底层系统 | 简单限流、防刷 |
| 精度 | 高,支持微调突发 | 高,但过于刚硬 | 中,窗口边界存在毛刺 |
滑动窗口的边界容易被突破,比如限制1分钟100次,如果在第59秒和第61秒各来100次,两个窗口都合法,但整体看已经超了,令牌桶不存在这个问题,它天然按时间连续累计,所以大多数业务接口限流方案对比的结论是:优先选令牌桶。
令牌桶的短板与应对手段
令牌桶唯一的短板是允许突发,桶容量设置太大会让流量瞬间冲垮下游,但这可以通过控制容量来解决,具体做法:
- 把桶容量设为正常速率的10%到20%。
- 例如接口上限是每秒1000请求,桶容量设100到200,这样最多只允许多出100到200个突发请求。
- 同时配合超时重试机制,被拒绝的请求在客户端退避后重试。
如果面对的是恶意攻击,令牌桶并不能区分正常用户和爬虫,此时应叠加IP黑白名单、用户维度的独立桶,或者使用更细粒度的限流策略。
高并发场景下令牌桶限流参数怎么设置
参数设置直接决定限流效果,数值拍脑袋是不行的,需要从容量规划推导。
四个关键参数
-

每秒填充速率(rate)
:对应系统能长期承受的QPS,通常取线上压测峰值的70%到80%,保留安全余量。 - 桶容量(capacity):允许的瞬时最大突发量,比如需要支持突然涌入500个请求,容量至少500。
- 初始令牌数:服务启动时桶是全满还是空?多数场景设为全满,避免刚启动就误杀请求。
- 拒绝策略:直接返回错误码、排队等待,还是降级返回缓存数据。
业内专家指出,设置参数前必须做一轮容量评估,用日志分析当前接口的日均调用量、峰值QPS、单次请求平均耗时,再结合下游数据库连接池大小定限流阈值。
一个具体计算案例
假设订单接口压测时最大QPS为2000,数据库连接池只有100个连接,每个查询耗时5毫秒,那么理论最大吞吐为100 / 0.005 = 20000 QPS,但为了稳定性:
- rate设为1500。
- capacity设为300,允许高峰时有1500之外的额外突发。
- 当令牌不足时,返回HTTP 429并附带重试时间。
实际运行后观察响应时间曲线,如果平均耗时开始线性上升,说明限流阈值偏高,需要逐步调低rate,如果便宜时段大量请求被拒,说明桶容量不够,稍微调大,这是一个持续调优的过程。
Redis令牌桶实现要点
在分布式环境中,本地令牌桶无法做到全局统一,这时需要用Redis的Lua脚本来保证原子性。
常用数据结构是key存令牌数,key存上次更新时间,Lua脚本执行以下逻辑:
- 获取当前令牌数和时间。
- 计算补充后的令牌数,取min(容量, 令牌数 + (now - last) rate)。
- 如果令牌数小于1,返回0。
- 否则令牌数减1,写回Redis,返回1。
为什么用Lua?因为Redis单线程执行Lua脚本可以避免并发竞争,如果不小心在多个服务节点各自实现了一套令牌桶,网关层就会出现动作不一,限流效果大打折扣。
令牌桶在典型业务场景中的落地方案
光懂算法还不够,还要知道在哪里写代码,下面是三种常见落点。
网关层集中限流
适合所有流量经过同一入口的微服务架构,Spring Cloud Gateway内置了RequestRateLimiter过滤器,底层默认使用令牌桶实现,配置路径如下:
- 在application.yml里开启filter。
- 定义RedisRateLimiter,设置 replenishRate(每秒补充速率)和 burstCapacity(桶容量)。
- 校验返回值:如果被限流,返回自定义JSON。
这种方案的好处是所有接口统一管理,运维方便,缺点是粒度粗,无法针对单个用户或IP做差异处理。

业务代码局部限流
当某个核心服务需要独立保护,可以用Google Guava的RateLimiter,但要注意,Guava的RateLimiter是单机版,需要根据节点数量分摊阈值,比如总计QPS限制1000,有5个节点,每个节点按200设置。
RateLimiter.create(200) 默认支持突发,可以通过 acquire() 阻塞或 tryAcquire() 快速失败,如果追求更精确的令牌桶,建议手动实现类似上文代码,因为Guava的实现细节更接近“平滑突发限制”,和标准令牌桶稍有不同。
Nginx Lua限流
OpenResty提供了 ngx.var.limit_rate 和 ngx_http_limit_req_module,limit_req 用到的是漏桶算法,若要用令牌桶,可以结合 lua-resty-limit-traffic 模块中的resty.limit.redis,配置示例:
- location /api/ 内编写access_by_lua_block。
- 实例化token bucket对象,传入容量和补充速率。
- 每次请求尝试取出令牌,失败则返回503。
这种方案性能最高,适合QPS非常大的场景,不过它对Lua编程能力有一定要求,小团队可能需要权衡成本。
常见问题快速解答
令牌桶限流会不会影响正常用户的并发体验?
不会,只要正确设置桶容量,正常用户的并发数远低于阈值,限流主要拦截的是异常主动访问,比如脚本刷单、爬虫、瞬时热点带来的大量重复请求,系统抖动时短暂拒绝,比直接打挂服务好得多。
单机令牌桶和Redis令牌桶怎么选?
如果服务只有单实例且没有水平扩容计划,直接使用本地令牌桶,性能和可用性都更高,如果服务部署了多个实例共用一个负载均衡入口,则必须用Redis令牌桶,否则每个实例各自放行,总流量仍然可能超过系统承载,选择Redis后要注意Redis本身的单点故障,建议用哨兵模式或集群版。
限流被拒绝后,客户端应该怎么处理?
默认情况下,网关返回HTTP 429 Too Many Requests,并在响应头带上 Retry-After 告诉客户端多久后重试,客户端发现429后应当指数退避,从1秒开始重试,最多退避到10秒,不要做无间隔重试,否则会持续占用限流器资源,合理的重试策略能显著提高整体吞吐。
接口限流的价值在于把系统从“死扛”变成“有序拒绝”,令牌桶算法既不会让流量完全平滑得像死水,也不会让系统被突发击穿,掌握原理、参数和分布式实现,这套方案足以覆盖绝大多数接口保护需求。