当前位置: 首页 > news >正文

令牌桶算法——流量控制和限流

背景
近期在做调用第三方系统接口的业务场景时,经常会出现接口超时的问题,跟第三方系统一番扯皮后发现原来是他们做了限流,限制了我们一秒钟只能请求2次,所以需要我们自己做好限流,控制我们的请求频率。

令牌桶算法(Token Bucket Algorithm)是一种常用的流量控制和限流算法,广泛用于网络传输、API 限流、系统资源管理等场景中。它通过一个“桶”来模拟请求的处理能力,并以固定的速率往桶中放入令牌(token),只有拿到令牌的请求才能被处理。


🧠 基本原理

令牌桶算法的核心思想是:

  • 桶中可以存放一定数量的“令牌”。
  • 系统以固定速率往桶中添加令牌(比如每秒添加10个令牌)。
  • 当有请求到来时,必须从桶中取出一个令牌,如果桶中没有令牌,则请求被拒绝或等待。
  • 桶有容量上限,超过容量的令牌会被丢弃。

这样就可以限制请求的平均速率,同时允许一定程度的突发流量(因为桶中可以存储令牌)。


🔍 特点

特性描述
平均速率限制可以限制请求的平均速率
支持突发流量如果桶中有积压的令牌,可以在短时间内处理大量请求
实现简单逻辑清晰,容易实现
非阻塞可以选择是否等待令牌

📦 示例说明

假设我们有一个令牌桶:

  • 容量为 10 个令牌;
  • 每秒添加 2 个令牌;
  • 请求需要获取令牌才能执行。

场景举例:

  1. 正常情况:每秒最多处理 2 个请求;
  2. 空闲后突发:如果前几秒没有请求,桶里积累了多个令牌,这时可以处理突发的多个请求;
  3. 超出限制:当请求速度过快,桶中没有令牌时,后续请求将被拒绝。

💻 Java 实现示例

下面是两个简单的 Java 实现

非阻塞,获取不到令牌就直接拒绝:

import java.util.concurrent.atomic.AtomicLong;public class TokenBucket {// 每秒生成的令牌数private final long capacity;// 桶的最大容量private final long rate;// 当前令牌数量private AtomicLong tokens = new AtomicLong(0);// 上一次补充令牌的时间private long lastRefillTime = System.currentTimeMillis();public TokenBucket(long rate, long capacity) {this.rate = rate;this.capacity = capacity;this.tokens.set(capacity); // 初始填满}/*** 尝试获取一个令牌*/public synchronized boolean tryConsume() {refill();if (tokens.get() > 0) {tokens.decrementAndGet();return true;}return false;}/*** 根据时间差补充令牌*/private void refill() {long now = System.currentTimeMillis();long timeElapsed = now - lastRefillTime;// 计算应该补充的令牌数long tokensToAdd = (timeElapsed * rate) / 1000; // 毫秒转秒if (tokensToAdd > 0) {lastRefillTime = now;long newTokens = Math.min(tokens.get() + tokensToAdd, capacity);tokens.set(newTokens);}}public static void main(String[] args) throws InterruptedException {TokenBucket bucket = new TokenBucket(2, 5); // 每秒2个令牌,最多存5个for (int i = 0; i < 10; i++) {if (bucket.tryConsume()) {System.out.println("Request " + (i + 1) + " processed.");} else {System.out.println("Request " + (i + 1) + " rejected.");}Thread.sleep(200); // 模拟请求频率}}
}

阻塞,获取不到令牌就等待执行:

package org.ffjy.lld.ffcrm.common;import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;public class TokenBucket {// 桶的最大容量private final long capacity;// 每秒生成的令牌数private final long rate;// 当前令牌数量private AtomicLong tokens = new AtomicLong(0);// 上一次补充令牌的时间private long lastRefillTime = System.currentTimeMillis();// 使用锁和条件变量实现等待机制private final Lock lock = new ReentrantLock();private final Condition notEmpty = lock.newCondition();public TokenBucket(long rate, long capacity) {this.rate = rate;this.capacity = capacity;this.tokens.set(capacity); // 初始填满}/*** 获取一个令牌,如果没有令牌则等待*/public void acquire() throws InterruptedException {lock.lock();try {while (true) {refill();if (tokens.get() > 0) {tokens.decrementAndGet();return;}// 计算需要等待的时间(毫秒)long now = System.currentTimeMillis();long timeUntilRefill = 1000 / rate; // 下一次补充令牌的时间间隔long waitTime = Math.max(0, lastRefillTime + timeUntilRefill - now);// 等待直到有新的令牌补充或超时notEmpty.await(waitTime, TimeUnit.MILLISECONDS);}} finally {lock.unlock();}}/*** 根据时间差补充令牌*/private void refill() {long now = System.currentTimeMillis();long timeElapsed = now - lastRefillTime;// 计算应该补充的令牌数long tokensToAdd = (timeElapsed * rate) / 1000; // 毫秒转秒if (tokensToAdd > 0) {lastRefillTime = now;long newTokens = Math.min(tokens.get() + tokensToAdd, capacity);tokens.set(newTokens);// 唤醒所有等待线程notEmpty.signalAll();}}public static void main(String[] args) throws InterruptedException {TokenBucket bucket = new TokenBucket(2, 5); // 每秒2个令牌,最多存5个for (int i = 0; i < 10; i++) {bucket.acquire();System.out.println("Request " + (i + 1) + " processed.");}}
}

⚙️ 与漏桶算法的区别(Guava 的 RateLimiter 使用的是平滑化的令牌桶)

对比项令牌桶漏桶
控制方式控制请求是否能拿取令牌控制请求流出的速度
流量特性允许突发流量强制平滑输出
实现复杂度简单较复杂
应用场景API限流、网络带宽控制需要严格控制输出速率的系统

🛠 实际应用

  • Spring Cloud Gateway / Zuul:做网关限流
  • Nginx:基于令牌桶实现请求限速
  • Guava RateLimiter:使用了改进版的令牌桶算法
  • 分布式系统限流:结合 Redis 实现分布式令牌桶

✅ 总结

令牌桶算法是一种非常实用的限流机制,具有以下优点:

  • 能够限制请求的平均速率
  • 支持一定的突发流量
  • 实现简单,性能好

在实际开发中,推荐结合使用如 Guava 的 RateLimiter 或 Spring Cloud Gateway 的限流组件,但在理解其背后原理(如令牌桶)之后,你可以根据业务需求进行定制化开发。

相关文章:

  • 【AI News | 20250526】每日AI进展
  • SpringAI(GA):Tool工具整合—快速上手
  • 如何实现 C/C++ 与 Python 的通信
  • 云化全场景+AI智算双擎驱动,打造高教数智化转型新范式,麒麟信安闪耀第63届高等教育博览会!
  • 25盘古石初赛wp(部分)
  • 回调函数的使用
  • Android磁盘占用优化全解析:从监控到治理的存储效率革命
  • [特殊字符]《Qt实战:基于QCustomPlot的装药燃面动态曲线绘制(附右键菜单/样式美化/完整源码)》
  • 基于GitHub Actions+SSH+PM2的Node.js自动化部署全流程指南
  • 华为云Flexus+DeepSeek征文 | 体验简单高效的模型推理开通之旅
  • 如何做好一份技术文档
  • Python入门手册:循环
  • 【人工智能-agent】--使用python调用dify工作流
  • AI学习笔记二十八:使用ESP32 CAM和YOLOV5实现目标检测
  • 【Java工程师面试全攻略】Day2:Java集合框架面试全解析
  • 机试 | STL | string | 文字处理软件
  • LVGL显示其他大小的中文
  • word的页眉页脚设置
  • RNN 在时序数据处理中的核心作用
  • 多语言视角下的 DOM 操作:从 JavaScript 到 Python、Java 与 C#
  • 腾讯云学生怎么做网站的/百度旗下的所有产品
  • 做音乐分享的网站/aso优化师
  • 上海网站的建设/阿里云万网域名查询
  • 做网站业务员怎么查找客户/百度app怎么找人工客服
  • 深圳网站建设在哪里找/地推
  • 怎么样查看网站开发语言/文员短期电脑培训