令牌桶限流算法

你提供的 Java 代码实现的是令牌桶限流算法(Token Bucket Algorithm),这是目前最常用、最灵活的限流算法之一。它允许一定程度的“突发流量”,同时又能控制平均速率。

下面我将:

  1. 逐行详细解释 TokenBucketLimiter 类的每一行代码逻辑和作用
  2. 分析 test4() 方法的运行流程和输出预期
  3. 指出当前实现的优点与潜在问题
  4. 提供优化建议和改进版本

✅ 一、TokenBucketLimiter 类逐行解析(带注释优化建议)

/*** 令牌桶算法限流实现*/
public class TokenBucketLimiter {// 上一次补充令牌的时间(毫秒)private long lastTime = System.currentTimeMillis();/*** 桶的容量* 作用:表示桶最多能存放多少个令牌* 当桶满时,新生成的令牌会被丢弃*/private long capacity;/*** 当前令牌数量* 作用:记录当前桶中可用的令牌数* 每次请求成功时,需从中减去 1 个令牌*/private long tokens;/*** 令牌放入速度,单位:个/秒* 作用:每秒向桶中添加多少个令牌* 例如 rate = 2 表示每秒生成 2 个令牌*/private long rate;/*** 构造函数:初始化令牌桶* @param capacity 桶的最大容量* @param rate 令牌生成速率(每秒生成多少个)*/public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = capacity; // 初始状态:桶是满的(常见设计)// 注:原代码未初始化 tokens,会导致初始为 0,这是个 Bug!}/*** 尝试获取一个令牌* @return true: 获取成功(放行),false: 获取失败(限流)* 注意:方法加了 synchronized,保证线程安全*/public synchronized boolean tryAcquire() {// 获取当前时间戳(毫秒)long now = System.currentTimeMillis();// 计算自上次请求以来,应该新增的令牌数量// (now - lastTime) 是毫秒差,除以 1000.0 得到秒数,乘以 rate 得到应生成的令牌数// 使用 Math.round 是为了四舍五入,避免浮点误差long increasedTokens = Math.round((now - lastTime) / 1000.0 * rate);System.out.println("now=" + now + ", lastTime=" + lastTime + ", increasedTokens=" + increasedTokens);// 更新当前令牌数:新增令牌,但不能超过桶的容量tokens = Math.min(capacity, tokens + increasedTokens);// 更新“上次补充令牌时间”为当前时间lastTime = now;// 判断是否有足够令牌(至少 1 个)if (tokens < 1) {// 没有可用令牌,拒绝请求return false;} else {// 有令牌,领取一个(消耗一个)tokens--;return true; // 放行请求}}
}

✅ 二、test4() 方法运行流程分析

@Test
public void test4() throws Exception {// 创建令牌桶:容量 10,每秒生成 2 个令牌TokenBucketLimiter limiter = new TokenBucketLimiter(10, 2);for (int i = 0; i < 36; i++) {if (i != 0 && i % 10 == 0) {Thread.sleep(500); // 每处理 10 个请求后,休眠 500ms}boolean tryAcquire = limiter.tryAcquire();if (tryAcquire) {System.out.println("请求" + i + "正常执行");} else {System.out.println("请求" + i + "被限流");}}
}

🧪 运行逻辑模拟(假设启动时间为 T)

  • 桶容量:10
  • 生成速率:2 个/秒
  • 每 10 次请求后休眠 500ms
问题:tokens 未初始化!

原代码中:

private long tokens; // 默认值为 0

→ 构造函数没有初始化 tokens → 初始 tokens = 0

这意味着:第一个请求来时,桶是空的!

这不符合令牌桶“允许突发”的设计初衷。通常应初始化为满桶


✅ 修正后逻辑(假设 tokens = capacity = 10 初始化)

前 10 个请求(i=0~9):
  • 请求密集到来,间隔极短
  • now ≈ lastTime → increasedTokens ≈ 0
  • tokens 从 10 递减到 1 → 全部放行
i=10:
  • 执行 Thread.sleep(500),休眠 500ms
  • 下一次请求(i=11)时,距离上次已过去 ~500ms
  • increasedTokens = round(0.5 * 2) = 1
  • tokens = min(10, 0 + 1) = 1(假设之前已用完)
  • tokens >= 1 → 放行,tokens = 0
后续请求:
  • 若请求间隔短,increasedTokens ≈ 0tokens 仍为 0 → 被限流
  • 每过 500ms 可生成 1 个令牌,可放行 1 次

✅ 预期输出(修正后):

请求0正常执行
请求1正常执行
...
请求9正常执行
请求10被限流
请求11正常执行
请求12被限流
请求13被限流
...
请求35被限流

实际上,由于休眠 500ms,每 500ms 可生成 1 个令牌,所以大约每 500ms 能处理 1 个请求。


⚠️ 三、当前实现的问题与优化建议

❌ 问题1:tokens 未初始化(严重 Bug)

private long tokens; // 默认为 0

→ 第一个请求来时,桶是空的!

修复:在构造函数中初始化为满桶:

this.tokens = capacity;

❌ 问题2:使用 Math.round 可能导致“多补令牌”

long increasedTokens = Math.round((now - lastTime) / 1000.0 * rate);
  • 四舍五入可能导致:本应补 0.6 个,结果补了 1 个 → 变相提高速率
  • 尤其在高并发、高频调用下,可能累积误差

建议:使用 Math.floor 或直接用 double 累积


✅ 优化建议1:使用 double 提高精度(推荐)

private double tokens = 0.0; // 支持小数令牌public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();double seconds = (now - lastTime) / 1000.0;double newTokens = seconds * rate;tokens = Math.min(capacity, tokens + newTokens);lastTime = now;if (tokens >= 1.0) {tokens -= 1.0;return true;} else {return false;}
}

更精确,避免浮点舍入误差,适合高并发场景。


❌ 问题3:synchronized 可能成为性能瓶颈

  • 所有请求串行化判断,影响吞吐量
  • 在高并发下,建议使用 AtomicLong + CAS 实现无锁化

✅ 优化建议2:无锁版本(使用 AtomicLong

import java.util.concurrent.atomic.AtomicLong;public class TokenBucketLimiter {private final long capacity;private final double rate;private final AtomicLong tokens = new AtomicLong();private final AtomicLong lastTime = new AtomicLong(System.currentTimeMillis());public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens.set(capacity); // 初始满桶this.lastTime.set(System.currentTimeMillis());}public boolean tryAcquire() {while (true) {long now = System.currentTimeMillis();long prevTime = lastTime.get();double seconds = (now - prevTime) / 1000.0;double newTokens = seconds * rate;long prevTokens = tokens.get();long updatedTokens = (long) Math.min(capacity, prevTokens + newTokens);if (updatedTokens >= 1) {if (tokens.compareAndSet(prevTokens, updatedTokens - 1) &&lastTime.compareAndSet(prevTime, now)) {return true;}} else {if (lastTime.compareAndSet(prevTime, now)) {return false;}}// CAS 失败,重试}}
}

更高性能,适合高并发场景。


✅ 四、总结

项目说明
算法类型令牌桶算法(Token Bucket)
核心思想系统以固定速率生成令牌,请求需“领取令牌”才能执行
优点支持突发流量(桶满时可瞬间处理多个请求),输出速率灵活
缺点实现稍复杂,需注意浮点精度和线程安全
适用场景API 限流、网关、高并发服务(如秒杀)
对比漏桶令牌桶允许突发,漏桶输出更平滑

🔁 令牌桶 vs 漏桶 对比

特性令牌桶漏桶
是否允许突发✅ 允许(桶满时可突发)❌ 不允许(恒定输出)
输出速率可变(突发时快)恒定
实现难度中等中等
适用场景高并发、允许突发平滑限流、防止冲击

结论

你的代码基本正确,但存在一个关键 Bug:tokens 未初始化,导致第一个请求可能被错误限流。

✅ 推荐修复后的版本:

public class TokenBucketLimiter {private long lastTime = System.currentTimeMillis();private final long capacity;private long tokens;private final long rate;public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = capacity; // 修复:初始化为满桶}public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();long increasedTokens = (long) Math.floor((now - lastTime) / 1000.0 * rate); // 改用 floortokens = Math.min(capacity, tokens + increasedTokens);lastTime = now;if (tokens >= 1) {tokens--;return true;} else {return false;}}
}

如果你需要,我也可以提供基于 Redis 的分布式令牌桶实现,适用于集群环境。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92085.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot的宠物商城设计与实现

管理员&#xff1a;登录&#xff0c;个人中心&#xff0c;用户管埋&#xff0c;宠物分类管理&#xff0c;宠物信息管理&#xff0c;留言反馈&#xff0c;宠物论坛&#xff0c;系统管理&#xff0c;订单管理用户&#xff1a;宠物信息&#xff0c;宠物论坛&#xff0c;公告信息&a…

Python day36

浙大疏锦行 Python day36. 复习日 本周内容&#xff1a; 如何导入模块以及库项目的规范拆分和写法官方文档的阅读MLP神经网络的训练在GPU上训练模型可视化以及推理

【gaussian-splatting】用自己的数据复现高斯泼溅(一)

1.环境准备1.1.下载diff-gaussian-rasterization这里本来没啥说的&#xff0c;直接从github上下载就行了&#xff0c;但是我踩坑了&#xff0c;下的版本不对&#xff0c;后续运行报错参数个数对不上&#xff0c;特在此给大家避坑&#xff0c;注意一定要下带3dgs版本的diff-gaus…

中国移动h10g-01_S905L处理器安卓7.1当贝纯净版线刷机包带root权限_融合终端网关

下载固件之前请先将主板上的屏蔽罩取下&#xff0c;查看处理器型号 是否为S905L型号&#xff0c;然后再下载固件进行刷机&#xff1b; 本页面的固件是采用双公头数据线进行刷机的哈&#xff1b; 安卓4.4.2版本固件下载地址&#xff1a;点此进行下载 安卓7.1版本固件下载地址…

夜天之书 #110 涓滴开源:Cronexpr 的故事

在年初的一篇关于商业开源的博文当中&#xff0c;我介绍了在开发商业软件的过程中&#xff0c;衍生出开源公共软件库的模式。在那篇博文里面&#xff0c;我只是简单罗列了相关开源库的名字及一句话总结。近期&#xff0c;我会结合商业开源实践的最新进展&#xff0c;对其中一些…

完整的登陆学生管理系统(配置数据库)

目录 要求 思路 1. 登录模块&#xff08;LoginFrame.java&#xff09; 2. 学生信息管理模块&#xff08;StudentFrame.java&#xff09; 3. 数据层&#xff08;StudentDAO.java&#xff09; 4. 业务层&#xff08;StudentService.java / UserService.java&#xff09; 5…

译 | 在 Python 中从头开始构建 Qwen-3 MoE

文章出自&#xff1a;基于 2个Expert 的 MoE 架构分步指南 本篇适合 MoE 架构初学者。文章亮点在于详细拆解 Qwen 3 MoE 架构&#xff0c;并用简单代码从零实现 MoE 路由器、RMSNorm 等核心组件&#xff0c;便于理解内部原理。 该方法适用于需部署高性能、高效率大模型&#x…

Spring Boot + ShardingSphere 分库分表实战

&#x1f680;Spring Boot ShardingSphere 实战&#xff1a;分库分表&#xff0c;性能暴增的终极指南&#xff01; ✅ 适用场景&#xff1a;千万级大表、高并发、读写分离场景 ✅ 核心框架&#xff1a;Spring Boot 3.x ShardingSphere-JDBC 5.4.1 ✅ 数据库&#xff1a;MySQL…

MaxKB 使用 MCP 连接 Oracle (免安装 cx_Oracle 和 Oracle Instant Client)

一、背景 安装cx_Oracle包和Oracle Instant Client来操作数据库&#xff0c;比较繁琐同时容易冲突&#xff0c;不同的 Oracle 版本都需要安装不同的插件。这篇文章将介绍使用 MCP 协议的连接方法。 二、操作步骤 1、使用 1Panel 安装 DBhub a) 数据库类型选择 Oracle 类型。…

基于Python的超声波OFDM数字通信链路设计与实现

基于Python的超声波OFDM数字通信链路设计与实现 摘要 本文详细介绍了使用Python实现的超声波OFDM(正交频分复用)数字通信链路系统。该系统能够在标准音响设备上运行&#xff0c;利用高于15kHz的超声波频段进行数据传输&#xff0c;采用48kHz采样率。文章涵盖了从OFDM基本原理、…

滑动窗口相关题目

近些年来&#xff0c;我国防沙治沙取得显著成果。某沙漠新种植N棵胡杨&#xff08;编号1-N&#xff09;&#xff0c;排成一排。一个月后&#xff0c;有M棵胡杨未能成活。现可补种胡杨K棵&#xff0c;请问如何补种&#xff08;只能补种&#xff0c;不能新种&#xff09;&#xf…

Java 工具类的“活化石”:Apache Commons 核心用法、性能陷阱与现代替代方案

在上一篇文章中&#xff0c;我们回顾了 Apache Commons 的经典组件。但作为 Java 世界中资历最老、影响最深远的工具库&#xff0c;它的价值远不止于此。许多开发者可能只使用了它 10% 的功能&#xff0c;却忽略了另外 80% 能极大提升代码质量的“隐藏宝石”。本文将提供一个更…

数据结构——图及其C++实现 多源最短路径 FloydWarshall算法

目录 一、前言 二、算法思想 三、代码实现 四、测试 五、源码 一、前言 前两篇学习的Dijkstra算法和Bellman-Ford算法都是用来求解图的单源最短路径&#xff0c;即从图中指定的一个源点出发到图中其他任意顶点的最短路径。Dijkstra算法不能求解带有负权重的图的最短路径&…

解决微软应用商店 (Microsoft store) 打不开,无网络连接的问题!

很多小伙伴都会遇见微软应用商店 (Microsoft store)打开后出现无网络的问题&#xff0c;一般出现这种问题基本都是因为你的电脑安装了某些银行的网银工具&#xff0c;因为网银工具为了安全会关闭Internet 选项中的最新版本的TLS协议&#xff0c;而微软商店又需要最新的TLS协议才…

Android—服务+通知=>前台服务

文章目录1、Android服务1.1、定义1.2、基本用法1.2.1、定义一个服务1.2.2、服务注册1.2.3、启动和停止服务1.2.4、活动和服务进行通信1.3、带绑定的服务示例1.3.1、定义服务类1.3.2、客户端&#xff08;Activity&#xff09;绑定与交互​1.3.3、AndroidManifest.xml 注册​1.3.…

从基础功能到自主决策, Agent 开发进阶路怎么走

Agent 开发进阶路线大纲基础功能实现核心模块构建环境感知&#xff1a;传感器数据处理&#xff08;视觉、语音、文本等输入&#xff09;基础动作控制&#xff1a;API调用、硬件驱动、简单反馈机制状态管理&#xff1a;有限状态机&#xff08;FSM&#xff09;或行为树&#xff0…

《动手学深度学习》读书笔记—9.6编码器-解码器架构

本文记录了自己在阅读《动手学深度学习》时的一些思考&#xff0c;仅用来作为作者本人的学习笔记&#xff0c;不存在商业用途。 正如我们在9.5机器翻译中所讨论的&#xff0c;机器翻译是序列转换模型的一个核心问题&#xff0c;其输入和输出都是长度可变的序列。为了处理这种类…

DocBench:面向大模型文档阅读系统的评估基准与数据集分析

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 一、数据集概述与核心目标 DocBench 是由研究团队于2024年提出的首个…

Python高级排序技术:非原生可比对象的自定义排序策略详解

引言&#xff1a;超越原生比较操作的排序挑战在Python数据处理中&#xff0c;我们经常需要处理不原生支持比较操作的对象。根据2024年《Python开发者生态系统报告》&#xff0c;在大型项目中&#xff0c;开发者平均需处理28%的自定义对象排序需求&#xff0c;这些对象包括&…

低代码系统的技术深度:超越“可视化操作”的架构与实现挑战

在很多非开发者眼中&#xff0c;低代码平台似乎只是简化流程、快速搭建页面的工具。然而&#xff0c;在真实的企业级应用中&#xff0c;低代码系统必须面对高并发请求、复杂业务规则、多角色权限、跨系统集成与持续演进等一系列工程挑战。高效交付&#xff08;Rapid Delivery&a…