漏桶和令牌桶有啥区别?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
- 限流基础概念:面试官不仅仅是想知道你有没有听过这两个算法,更想看你是否理解高并发场景下为什么要限流,以及两大经典算法各自的定位
- 算法原理理解:能否准确说出漏桶和令牌桶的工作机制,而不是停留在 “知道名字” 的层面
- 生产选型能力:能不能根据业务特点(突发流量 vs 平滑流量)选对算法,有没有实际落地经验
核心答案
漏桶和令牌桶都是限流算法,但思路完全不一样。
漏桶:请求像水一样倒进桶里,桶底有个固定大小的洞,水以恒定速率漏出。不管你倒水多猛,漏出去的速度永远不变——强制平滑流量,不允许突发。
令牌桶:桶里以固定速率往里放令牌,请求来了先从桶里拿一个令牌,拿得到就处理,拿不到就拒绝或排队。桶有上限,攒满了就不再放——允许一定程度的突发流量。
最核心的区别一句话:漏桶出去的是匀速的,令牌桶进来的是匀速的(但出去可以快)。
| 对比项 | 漏桶 | 令牌桶 |
|---|---|---|
| 核心思想 | 出口恒速 | 入口匀速放令牌 |
| 突发流量 | 不允许,强制平滑 | 允许,攒够令牌就能瞬时通过 |
| 流量整形 | 强 | 弱 |
| 实现复杂度 | 简单 | 稍复杂 |
| 适用场景 | 保护下游脆弱系统 | API 网关、开放平台 |
| 常见实现 | Nginx limit_req |
Guava RateLimiter、Spring Cloud Gateway |
深度解析
一、漏桶算法(Leaky Bucket)
漏桶你可以想象成一个真实的水桶,桶底有个小孔漏水。
上图就是漏桶的处理流程,归纳起来就三步:
- 入桶阶段:请求以任意速率到达,只要桶还没满就放进来,满了直接拒绝
- 出桶阶段:桶里的请求以固定速率被取出,送往下游处理
- 关键点:不管上游来得多猛,下游永远以匀速接收
漏桶最大的价值就是 强制整形——把崎岖不平的流量,强行削成一条平滑的直线。对下游非常友好。
但缺点也很明显:完全不能处理突发。哪怕下游其实有能力处理一波瞬时高并发,漏桶也会无情地把它削平。就算系统资源空闲,也只能干等着。
二、令牌桶算法(Token Bucket)
令牌桶换了个思路:不是控制水流出去,而是控制 “通行证” 的发放。
上图是令牌桶的核心流程,要点有这么几个:
- 令牌生成:以固定速率往桶里放令牌,桶有容量上限,满了就丢弃多余的
- 请求处理:每个请求来了,必须从桶里取走一个令牌才能通过,取不到就被拒绝
- 突发能力:如果一段时间没请求,桶里能攒满令牌;这时候来一波突发流量,能瞬间消耗掉所有令牌,相当于瞬时通过大量请求
令牌桶的关键在于:长期平均速率恒定,但短期允许突破。这个特性很贴合真实业务——大多数业务就是 “平时低峰 + 偶尔突发” 这个节奏。
三、用代码直观感受差异
用 Guava 的 RateLimiter 来体验一下令牌桶:
import com.google.common.util.concurrent.RateLimiter;
// 每秒发放 2 个令牌
RateLimiter limiter = RateLimiter.create(2.0);
// 突发请求:第一个直接放行(预消费机制)
for (int i = 0; i < 5; i++) {
System.out.println("任务 " + i + " 等待 " + limiter.acquire() + " 秒");
}
输出大致是:
任务 0 等待 0.0 秒 // 第一个请求直接放行(预消费)
任务 1 等待 0.5 秒 // 间隔 1/2 = 0.5s
任务 2 等待 0.5 秒
任务 3 等待 0.5 秒
任务 4 等待 0.5 秒
可以看到 Guava 的 RateLimiter 还做了 “预消费” 优化——第一个请求不用等,先放行,后面再慢慢还。令牌桶灵活就灵活在这。
源码层面,RateLimiter 通过记录 nextFreeTicketMicros(下次可获取令牌的时间)和 stableIntervalMicros(令牌间隔),用时间差来计算该补多少令牌,而不是真的开个线程定时发令牌。这个设计挺巧妙的,省掉了定时器的开销。
四、主流框架用的是哪个
这块面试官特别爱问 “你用过哪个限流框架”。实际项目里这两个算法的分布大致是这样:
- Guava
RateLimiter:令牌桶。两个子类SmoothBursty(平滑突发)和SmoothWarmingUp(带预热),后者适合 DB 连接池这种冷启动场景 - Spring Cloud Gateway:令牌桶,基于 Redis + Lua 脚本实现分布式限流,核心参数
replenishRate(填充速率)和burstCapacity(桶容量) - Nginx
limit_req:漏桶。配合burst和nodelay参数可以微调突发处理策略 - Sentinel:默认是 滑动时间窗口算法(不是令牌桶!),但在 “排队等待” 模式下用的是漏桶思想(
RateLimiterController),“预热” 模式用的令牌桶(WarmUpController)
最后这点很多面试者会答错,记住 Sentinel 默认是滑动窗口,能加分。
五、生产环境怎么选
- API 网关、对外暴露的接口:选令牌桶。外部流量波动大,得允许合理的突发
- 保护下游 DB、消息队列:可以选漏桶。比如数据库连接池的请求限流,宁愿慢一点也要匀速,避免压垮 DB
- 消息队列消费端:经常用漏桶思想,匀速消费避免冲击下游
说句实在话,生产里 令牌桶用得更多,因为大多数场景需要的是 “限制平均速率,允许短期突发”,而不是死板的匀速。
面试高频追问
-
令牌桶和漏桶到底哪个更好?
没有绝对的好坏。允许突发选令牌桶,要严格匀速选漏桶。看业务。
-
Guava
RateLimiter是漏桶还是令牌桶?令牌桶。它还做了一层 “预消费” 优化,可以提前透支令牌,处理突发更友好。
-
分布式场景下怎么做限流?
单机的
RateLimiter只能管一个 JVM。分布式要用 Redis + Lua 实现令牌桶(Spring Cloud Gateway 就是这么干的),或者直接上 Sentinel 集群限流。 -
限流和熔断降级有什么区别?
限流是 “请排队进来”;熔断是 “我直接不去了,你找别人吧”。限流防涌入,熔断防拖垮。
常见面试变体
- “你知道哪些限流算法?”
- “Sentinel 默认用什么限流算法?”(坑题,答滑动窗口)
- “分布式限流怎么实现?”
- “令牌桶和漏桶在突发流量处理上有什么本质区别?”
记忆口诀
漏桶出匀速,令牌桶进匀速——记住这个,本质区别就抓住了。
- 漏桶 = 漏水恒速,强制平滑,不允许突发
- 令牌桶 = 放令牌恒速,攒着能用,允许突发
总结
漏桶和令牌桶是限流的两大经典算法。漏桶出口恒速,强制流量整形,适合保护下游;令牌桶入口匀速放令牌,允许突发,更适合真实业务的波动特征。生产环境令牌桶用得更多,Guava RateLimiter、Spring Cloud Gateway 这些主流组件默认都是令牌桶实现。Nginx limit_req 则是漏桶的典型代表。
