news 2026/6/8 23:46:43

一文搞懂令牌桶限流:平均速率与突发流量的本质(分布式令牌桶实现思路)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文搞懂令牌桶限流:平均速率与突发流量的本质(分布式令牌桶实现思路)

一文搞懂令牌桶限流:平均速率与突发流量的本质(分布式令牌桶实现思路)

在做后端系统设计、网关限流或高并发控制时,令牌桶(Token Bucket)是一个绕不开的核心算法。很多人知道它“能限流”,但真正难理解的,是这句话:

令牌桶限制的是平均处理速率,但允许一定程度的突发流量

这篇文章将基于一个完整的问答推导过程,用时间线 + 场景的方式,帮你一次性真正搞懂这句话背后的含义。


一、先明确:令牌桶到底在干什么?

令牌桶算法的核心思想可以总结为一句话:

系统以固定速率往桶中放入令牌,请求只有拿到令牌才能被处理

这里有三个关键要素:

  • 令牌生成速率(rate):比如 100 个 / 秒
  • 桶容量(capacity):比如最多存 100 个令牌
  • 请求消耗令牌:每个请求通常消耗 1 个令牌

限流器本质上做的事情只有一件:

按照时间持续生成令牌,并维护桶中令牌数量不超过上限

当桶满时,新生成的令牌会被直接丢弃。


二、什么叫「限制平均处理速率」?

假设我们设置:

令牌生成速率:100 token / 秒 桶容量:100

这意味着什么?

  • 每 1 秒,系统最多新增 100 个可用处理资格(令牌)
  • 10 秒内,最多生成 1000 个令牌
  • 1 分钟内,最多生成 6000 个令牌

无论请求如何变化:

系统在较长时间尺度上的处理能力是被严格限制的

也就是说,你不可能让系统长期稳定地跑在 200 QPS、300 QPS。

这就是所谓的:

限制的是平均处理速率


三、那什么是「允许突发流量」?

关键就在于:令牌是可以提前积累的

我们还是用上面的参数,通过时间线来看。


四、完整时间线推演(核心理解)

阶段 1:系统启动后的 1 秒内没有请求

t = 0s ~ 1s
  • 限流器持续生成令牌
  • 桶容量是 100

结果:

桶中令牌 = 100(已满)

阶段 2:t = 1s,瞬间来了 100 个请求

注意关键词:瞬间(可能是 1ms 内)

处理结果:

  • 每个请求消耗 1 个令牌
  • 桶中正好有 100 个令牌
100 个请求 → 全部立即通过 桶中令牌 = 0

这一步非常重要:

虽然配置的是 100 QPS,但这一刻的并发远远超过了 100 QPS

这就是:

允许突发流量


五、这为什么不违反「100 QPS」?

因为QPS 是一个“时间平均值”概念

我们看一个完整时间段:

时间段处理请求数
第 0~1 秒0
第 1~2 秒100
合计100

平均下来:

100 / 2 = 50 QPS

完全没有超过限制。

令牌桶限制的是长期平均速率,而不是瞬时峰值。


六、突发之后会发生什么?

阶段 3:突发请求消耗完令牌后

t > 1s 桶中令牌 = 0

此时限流器仍然在工作:

  • 按速率继续生成令牌
  • 100 token / 秒 ≈ 每 10ms 生成 1 个

阶段 4:这时又瞬间来了 100 个请求

假设只过去了 10ms:

桶中令牌 ≈ 1

这 100 个请求会怎样?


七、两种工程实践结果(非常重要)

情况一:不允许等待(最常见于网关)

  • 1 个请求拿到令牌 → 立即通过
  • 剩余 99 个请求 → 直接被限流(返回 429)

此时你的理解:

“只能一个过去”

完全正确的


情况二:允许等待(服务内部常见)

  • 1 个请求立即通过
  • 剩余 99 个请求进入等待队列
  • 后续每生成 1 个令牌,就放行 1 个请求

最终表现为:

输出速率被平滑成 100 QPS

瞬时峰值被削掉,请求被“平滑接住”


八、那「允许一定程度」到底是多少?

答案只有一个:

桶容量(capacity)

  • 桶容量 = 100 → 最多允许瞬间 100 个请求
  • 桶容量 = 1000 → 最多允许瞬间 1000 个请求

公式级理解:

最大突发能力 = 桶容量

九、和漏桶算法的一句话对比

很多人会把令牌桶和漏桶混在一起,这里给你一句话区分:

令牌桶限制平均速率,允许突发;漏桶限制输出速率,不允许突发。


十、一句话总结

你可以这样总结令牌桶:

令牌桶算法通过限制令牌的生成速率来约束系统的长期平均处理能力,同时允许在桶容量范围内消耗提前积累的令牌,从而在短时间内承受一定规模的突发请求;当令牌耗尽后,请求将只能以固定速率被放行或直接被限流。

如果你理解到这里,说明你已经真正理解了:

什么叫“限制平均速率,但允许突发流量”


十一、Redis 分布式令牌桶实现思路(工程实践)

在单机内存中实现令牌桶并不复杂,但在多实例部署(微服务 / 网关集群)的场景下,就必须借助 Redis 来实现分布式限流,以保证多节点之间的令牌一致性。

下面给出一个经典且工程上可落地的 Redis 分布式令牌桶设计思路


1、 核心设计目标

使用 Redis 实现令牌桶,需要解决三个问题:

  1. 令牌全局唯一:多个实例共享同一个桶
  2. 原子性:生成令牌 + 消耗令牌不能被并发破坏
  3. 高性能:限流逻辑必须非常快

因此,Lua 脚本 + Redis 单线程原子执行是最优解。


2、 Redis 中需要维护的关键数据

通常使用一个 Redis Key 表示一个令牌桶,例如:

rate_limiter:{api_name}

该 Key 对应的数据结构中,需要维护:

  • tokens:当前桶中剩余令牌数
  • last_timestamp:上一次补充令牌的时间戳

逻辑上可以理解为:

(tokens, last_timestamp)

3、 请求到来时的完整执行流程

每个请求到来,都会在 Redis 中原子执行以下步骤

  1. 获取当前时间now
  2. 根据时间差计算应补充的令牌数:
new_tokens = (now - last_timestamp) * rate
  1. 更新桶中令牌数(不能超过容量):
tokens = min(capacity, tokens + new_tokens)
  1. 判断是否有足够令牌:
  • 如果tokens >= 1

    • tokens = tokens - 1
    • 请求放行
  • 否则:

    • 请求被限流
  1. 更新last_timestamp = now

以上逻辑必须在一个 Lua 脚本中完成,否则并发下会出现超发令牌的问题。


4、 Lua 脚本伪代码示例

下面是一个简化版伪代码(便于理解思路):

localrate=tonumber(ARGV[1])-- 每秒生成令牌数localcapacity=tonumber(ARGV[2])-- 桶容量localnow=tonumber(ARGV[3])localtokens=tonumber(redis.call('HGET',KEYS[1],'tokens'))orcapacitylocallast=tonumber(redis.call('HGET',KEYS[1],'last'))ornowlocaldelta=math.max(0,now-last)localnew_tokens=delta*rate tokens=math.min(capacity,tokens+new_tokens)iftokens<1thenredis.call('HSET',KEYS[1],'tokens',tokens)redis.call('HSET',KEYS[1],'last',now)return0elsetokens=tokens-1redis.call('HSET',KEYS[1],'tokens',tokens)redis.call('HSET',KEYS[1],'last',now)return1end

返回值:

  • 1→ 请求通过
  • 0→ 请求被限流

5、 为什么 Redis 令牌桶是“软限流”?

Redis 令牌桶本质上是:

  • 立即判断是否有令牌
  • 不负责请求排队

因此它通常用于:

  • API 网关限流
  • 外部接口防刷
  • 秒杀前置拦截

是否“等待令牌”通常由:

  • 客户端重试
  • 上层业务队列

来实现,而不是限流器本身。


6、 工程实践中的几个关键细节

  • 时间单位统一:建议统一使用毫秒时间戳

  • 桶初始化:第一次访问时默认令牌为capacity

  • Key 过期策略:可设置较长 TTL,避免冷 Key 堆积

  • 限流粒度

    • 按接口限流
    • 按用户限流
    • 按 IP 限流

7、 一句话工程总结

Redis 分布式令牌桶通过 Lua 脚本保证限流逻辑的原子性,使多实例系统在共享令牌的情况下,依然能够严格控制整体平均速率,同时保留令牌桶对突发流量的天然支持能力。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 21:42:45

EmotiVoice语音合成中的多音字准确识别率提升

EmotiVoice语音合成中的多音字准确识别率提升 在智能语音助手动辄“把‘银行’读成‘yn xng’”的年代&#xff0c;用户早已对TTS系统的“读错字”习以为常。然而&#xff0c;当AI开始朗读《红楼梦》或医学文献时&#xff0c;一个“重”字念错声调&#xff0c;就可能让听众瞬间…

作者头像 李华
网站建设 2026/6/5 17:45:50

Python 实战:手把手教你开发百度网盘全功能开发者工具

在数字化时代&#xff0c;自动化管理云盘文件是许多开发者的共同需求。百度网盘开放平台&#xff08;XPAN&#xff09;提供了强大的 API&#xff0c;但对于初学者来说&#xff0c;身份认证&#xff08;OAuth 2.0&#xff09;和权限校验&#xff08;尤其是令人头疼的 31064 错误…

作者头像 李华
网站建设 2026/5/28 21:50:43

Kotaemon前端交互界面开源项目推荐

Kotaemon&#xff1a;构建生产级智能对话系统的开源利器 在大模型能力日益普及的今天&#xff0c;越来越多企业开始尝试将 LLM 应用于客服、知识问答、技术支持等实际场景。但很快就会遇到一个共性问题&#xff1a;模型“说得漂亮”&#xff0c;却常常“答非所问”——给出的回…

作者头像 李华
网站建设 2026/6/8 19:43:33

LeetCode第2658题 - 网格图中鱼的最大数目

题目 解答 class Solution {public int findMaxFish(int[][] grid) {int maxCount Integer.MIN_VALUE;int m grid.length;int n grid[0].length;for (int i 0; i < m; i) {for (int j 0; j < n; j) {int value grid[i][j];if (value 0) {continue;}int count b…

作者头像 李华
网站建设 2026/6/5 20:45:32

EmotiVoice情感编码技术揭秘:语音合成如何传递情绪

EmotiVoice情感编码技术揭秘&#xff1a;语音合成如何传递情绪 在虚拟助手机械地念出“今天天气不错”的时候&#xff0c;你是否会感到一丝疏离&#xff1f;而在某款游戏中&#xff0c;NPC因你的靠近突然语气警觉、语速加快——那一刻&#xff0c;沉浸感悄然建立。这种差异背后…

作者头像 李华