漏桶和令牌桶有啥区别?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(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+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 限流基础概念:面试官不仅仅是想知道你有没有听过这两个算法,更想看你是否理解高并发场景下为什么要限流,以及两大经典算法各自的定位
  2. 算法原理理解:能否准确说出漏桶和令牌桶的工作机制,而不是停留在 “知道名字” 的层面
  3. 生产选型能力:能不能根据业务特点(突发流量 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:漏桶。配合 burstnodelay 参数可以微调突发处理策略
  • Sentinel:默认是 滑动时间窗口算法(不是令牌桶!),但在 “排队等待” 模式下用的是漏桶思想(RateLimiterController),“预热” 模式用的令牌桶(WarmUpController

最后这点很多面试者会答错,记住 Sentinel 默认是滑动窗口,能加分。

限流框架算法对比
限流框架算法对比

五、生产环境怎么选

  • API 网关、对外暴露的接口:选令牌桶。外部流量波动大,得允许合理的突发
  • 保护下游 DB、消息队列:可以选漏桶。比如数据库连接池的请求限流,宁愿慢一点也要匀速,避免压垮 DB
  • 消息队列消费端:经常用漏桶思想,匀速消费避免冲击下游

说句实在话,生产里 令牌桶用得更多,因为大多数场景需要的是 “限制平均速率,允许短期突发”,而不是死板的匀速。

漏桶令牌桶生产选型
漏桶令牌桶生产选型

面试高频追问

  1. 令牌桶和漏桶到底哪个更好?

    没有绝对的好坏。允许突发选令牌桶,要严格匀速选漏桶。看业务。

  2. Guava RateLimiter 是漏桶还是令牌桶?

    令牌桶。它还做了一层 “预消费” 优化,可以提前透支令牌,处理突发更友好。

  3. 分布式场景下怎么做限流?

    单机的 RateLimiter 只能管一个 JVM。分布式要用 Redis + Lua 实现令牌桶(Spring Cloud Gateway 就是这么干的),或者直接上 Sentinel 集群限流。

  4. 限流和熔断降级有什么区别?

    限流是 “请排队进来”;熔断是 “我直接不去了,你找别人吧”。限流防涌入,熔断防拖垮。

常见面试变体

  • “你知道哪些限流算法?”
  • “Sentinel 默认用什么限流算法?”(坑题,答滑动窗口)
  • “分布式限流怎么实现?”
  • “令牌桶和漏桶在突发流量处理上有什么本质区别?”

记忆口诀

漏桶出匀速,令牌桶进匀速——记住这个,本质区别就抓住了。

  • 漏桶 = 漏水恒速,强制平滑,不允许突发
  • 令牌桶 = 放令牌恒速,攒着能用,允许突发

总结

漏桶和令牌桶是限流的两大经典算法。漏桶出口恒速,强制流量整形,适合保护下游;令牌桶入口匀速放令牌,允许突发,更适合真实业务的波动特征。生产环境令牌桶用得更多,Guava RateLimiter、Spring Cloud Gateway 这些主流组件默认都是令牌桶实现。Nginx limit_req 则是漏桶的典型代表。