以下是令牌桶算法漏桶算法雪花算法的清晰对比解析。它们属于完全不同的技术领域,前两者用于流量控制,后者用于分布式ID生成


1. 令牌桶算法(Token Bucket)

  • 领域:流量整形 / 速率限制
  • 核心目标:控制请求处理速率,允许突发流量
  • 工作原理
    • 系统以固定速率生成令牌存入桶中(桶有容量上限)。
    • 请求到达时消耗令牌:
      • 令牌足够 → 立即处理
      • 令牌不足 → 拒绝或等待
  • 关键特性
    消耗令牌
    无令牌
    令牌生成
    令牌桶
    请求到达
    处理请求
    拒绝请求
  • 应用场景
    • API接口限流(如允许瞬间高并发)
    • 网络流量突发传输(视频直播)
    • 微服务熔断降级

2. 漏桶算法(Leaky Bucket)

  • 领域:流量整形 / 请求平滑
  • 核心目标:强制请求以恒定速率处理,消除突发性
  • 工作原理
    • 请求进入桶中(桶有容量上限)。
    • 桶底以固定速率“漏出”请求进行处理(FIFO)。
    • 桶满时新请求被拒绝。
  • 关键特性
    未满
    已满
    恒定速率漏出
    请求到达
    漏桶
    桶状态
    存入桶中
    拒绝请求
    处理请求
  • 应用场景
    • 网络设备发包速率控制
    • 打印机任务队列调度
    • 保护下游服务不被流量冲垮

3. 雪花算法(Snowflake)

  • 领域:分布式系统 全局唯一ID生成
  • 核心目标:在分布式环境下生成趋势递增、不重复的ID
  • ID结构(64位二进制):
    0 | 0000000000 0000000000 0000000000 0000000000 0 | 0000000000 | 000000000000
    └─符号位(恒0) └─────41位时间戳(毫秒)─────┘ └─10位机器ID─┘ └──12位序列号──┘
    
  • 工作原理
    1. 时间戳:记录ID生成时间(可支持69年)
    2. 机器ID:区分不同节点(防止集群冲突)
    3. 序列号:同一毫秒内的自增序号(支持4096/ms/节点)
  • 关键特性
    • 高性能:单机每秒可生成400万+ ID
    • 去中心化:无需协调服务(如数据库、ZooKeeper)
    • 有序性:ID按时间趋势递增(利于数据库索引)
  • 应用场景
    • 分布式数据库主键(MySQL、MongoDB)
    • 消息队列消息ID(Kafka、RocketMQ)
    • 业务订单号/用户ID生成

三者的本质区别总结

维度令牌桶算法漏桶算法雪花算法
领域流量控制流量控制分布式ID生成
核心目标允许突发,限平均速率强制平滑输出请求生成全局唯一有序ID
关键技术令牌生成与消耗请求队列+恒定速率泄漏时间戳+机器ID+序列号
是否存储状态是(令牌数)是(请求队列)是(时间戳/序列号计数)
典型应用API限流、网络流量整形打印机调度、流量平滑数据库主键、分布式业务ID

为什么雪花算法不用于流量控制?

  • 雪花算法的“桶”是逻辑结构(用于组合ID),而令牌桶/漏桶的“桶”是请求缓冲容器,两者解决的问题维度完全不同。

💡 技术选型建议

  • 需要应对突发流量 → 令牌桶(如电商秒杀)
  • 严格平滑输出 → 漏桶(如支付回调)
  • 分布式唯一ID → 雪花算法(如订单系统)

接口:

public interface SourceAble {void tokenBucket() throws Exception;void leakyBucket() throws Exception;
}
桶的适配器:
public abstract class BucketWrapper implements SourceAble {@Overridepublic void tokenBucket() throws Exception {System.out.println("抽象类的令牌桶算法");}@Overridepublic void leakyBucket() throws Exception {System.out.println("抽象类的漏桶算法");}}

令牌桶与漏桶分别实现桶抽象类:令牌桶

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenBucket extends BucketWrapper {/*** 平均流量(每秒生成的令牌数量)*/private int avgFlowRate = 512;/*** 一个令牌允许的数据包大小 1字节*/private int everyTokenSize = 1;/*** 桶内令牌上限1024个,最大流量峰值(瞬间最大流量)为 everyTokenSize*maxFlowRate =1024Byte=1K*/private int maxFlowRate = 1024;/*** 队列来缓存桶数量*/private ArrayBlockingQueue<Byte> tokenQueue = new ArrayBlockingQueue<>(maxFlowRate);/*** 任务调度,固定速率产生令牌*/private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();/*** volatile: 当一个变量被volatile修饰时,对该变量的写操作会立即被其他线程可见,即保证了可见性。* volatile关键字还可以禁止编译器和处理器对被修饰变量的操作进行重排序优化。*/private volatile boolean isStart = false;/*** ReentrantLock(可重入锁)是Java中提供的一种高级并发锁机制,用于解决多线程并发访问共享资源时的同步和互斥控制问题。* ReentrantLock与synchronized关键字相比,提供了更灵活、更强大的锁定机制。*/private ReentrantLock lock = new ReentrantLock(true);public static TokenBucket newBuilder() {return new TokenBucket();}/*** 获取足够的令牌个数** @return boolean*/public boolean getTokens(byte[] dataSize) {// 传输内容大小对应的桶个数int needTokenNum = dataSize.length / everyTokenSize + 1;final ReentrantLock lock = this.lock;lock.lock();try {// 是否存在足够的桶数量boolean result = needTokenNum <= tokenQueue.size();System.out.println("桶内令牌数" + (result ? "充足" : "不足") + ",总令牌数:" +tokenQueue.size() + " 需要:" + needTokenNum);if (!result) {return false;}int tokenCount = 0;for (int i = 0; i < needTokenNum; i++) {Byte poll = tokenQueue.poll();if (poll != null) {tokenCount++;}}return tokenCount == needTokenNum;} finally {lock.unlock();}}/*** 添加令牌** @param tokenNum*/public void addTokens(Integer tokenNum) {for (int i = 0; i < tokenNum; i++) {// 若是桶已经满了,就不再加入新的令牌tokenQueue.offer((byte) 1);}}/*** build,完成初始化,并开始任务调度(始按照固定速率生产令牌)** @return*/public TokenBucket build() {start();return this;}public void start() {// 初始化桶队列大小if (maxFlowRate != 0) {tokenQueue = new ArrayBlockingQueue<>(maxFlowRate);}// 初始化令牌生产者TokenProducer tokenProducer = new TokenProducer(avgFlowRate, this);scheduledExecutorService.scheduleAtFixedRate(tokenProducer, 0, 1, TimeUnit.SECONDS);isStart = true;}@AllArgsConstructorstatic class TokenProducer implements Runnable {private int avgFlowRate;private TokenBucket tokenBucket;@Overridepublic void run() {tokenBucket.addTokens(avgFlowRate);}}public TokenBucket maxFlowRate(int maxFlowRate) {this.maxFlowRate = maxFlowRate;return this;}public TokenBucket avgFlowRate(int avgFlowRate) {this.avgFlowRate = avgFlowRate;return this;}@Overridepublic void tokenBucket() throws Exception {// 平均流量512,瞬时流量1024TokenBucket tokenBucket = TokenBucket.newBuilder().avgFlowRate(512).maxFlowRate(1024).build();// 四个字节String data = "0000";for (int i = 1; i <= 20; i++) {// 生成随机大小的流量请求int copyNum = new Random().nextInt(100);StringBuilder stringBuilder = new StringBuilder(data.length() * copyNum);for (int j = 0; j < copyNum; j++) {stringBuilder.append(data);}// 获取对应数量的令牌boolean tokens = tokenBucket.getTokens(stringBuilder.toString().getBytes());if (tokens) {// 拿到令牌,通过System.out.println("第 " + i + " 次请求通过,传输数据:" + copyNum+ "字节 桶内令牌数量:" + tokenBucket.tokenQueue.size());} else {// 没有拿到令牌,拒绝System.out.println("第 " + i + " 次请求拒绝,传输数据:" + copyNum+ "字节 桶内令牌数量:" + tokenBucket.tokenQueue.size());}TimeUnit.MILLISECONDS.sleep(100);}}
}

漏桶:

@EqualsAndHashCode(callSuper = false)
@Data
@NoArgsConstructor
public class LeakyBucket extends BucketWrapper {/*** 当前时间*/private Long lastLeakyTimeStamp = System.currentTimeMillis();/*** 桶的容量*/private Integer burst;/*** 水漏出的速度,每毫秒处理的数量*/private Double rate;/*** 当前水量(当前累积请求数)*/private Integer currentWater = 0;public LeakyBucket(int burst, double rate) {this.burst = burst;this.rate = rate;}/*** 漏水*/public void leaky() {long now = System.currentTimeMillis();// 间隔的时间long deltaTs = now - this.lastLeakyTimeStamp;// 漏掉的水int deltaQuota = (int) (deltaTs * rate);// 桶内剩余的水int leftWater = currentWater - deltaQuota;// 先执行漏水,计算剩余水量currentWater = Math.max(0, leftWater);//更新上一次漏水时间为当前时间lastLeakyTimeStamp = now;}/*** 加水(新请求)** @return 是否满水*/public boolean acquire(int size) {leaky();if (currentWater + size <= burst) {// 尝试加水,并且水还未满currentWater += size;return true;} else {// 水满,拒绝加水return false;}}@Overridepublic void leakyBucket() throws Exception {// 每100ms,流入10的水,流出5的水for (int i = 0; i < 100; i++) {Boolean acquire = acquire(10);System.out.println("本次申请:" + (acquire ? "成功" : "失败") +"当前桶内水量:" + currentWater +" 剩余空间:" + (burst - currentWater));TimeUnit.MILLISECONDS.sleep(100);}}
}

雪花算法:

@EqualsAndHashCode(callSuper = false)
@Data
public class SnowFlake extends BucketWrapper {/*** 因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。*/private long sequence = 0L;/*** 代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个,每毫秒生产的序列号之从0开始递增;*/private long sequenceBits = 12L;/*** 最大值4095,到最大后从0开始*/private long sequenceMask = ~(-1L << sequenceBits);/*** 10位机器码看成是“5位dataCenterId+5位workerId”*/private long workerId;/*** 机器ID  2进制5位  32位减掉1位 31个*/private long workerIdBits = 5L;private long workerIdShift = sequenceBits;/*** 机房ID 2进制5位  32位减掉1位 31个*/private long dataCenterId;private long dataCenterIdBits = 5L;private long dataCenterIdShift = sequenceBits + workerIdBits;private long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;/*** 记录上一个id的产生时间戳,判断是否在 同一毫秒内*/private long lastTimestamp = -1L;SnowFlake(long dataCenterId, long workerId) {// 检查机房id和机器id是否超过31 不能小于0// 就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内long maxWorkerId = ~(-1L << workerIdBits);long maxDataCenterId = ~(-1 << dataCenterIdBits);if ((dataCenterId > maxDataCenterId || dataCenterId < 0) || (workerId > maxWorkerId || workerId < 0)) {throw new IllegalArgumentException("dataCenterId/workerId值非法");}this.dataCenterId = dataCenterId;this.workerId = workerId;}/*** 通过SnowFlake生成id的核心算法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id** @return*/private synchronized long nextId() {//获取计算id时刻的时间戳long timestamp = System.currentTimeMillis();//保证递增if (timestamp < lastTimestamp) {throw new RuntimeException("时间戳值非法");}//如果在同一个毫秒内,又发送了一个请求生成一个id,这时把seqence序号给递增1,最多就是4096个(0-4095)if (lastTimestamp == timestamp) {/*通过位运算保证sequence不会超出序列号所能容纳的最大值4095如果到达最大值4095此时再增加一个(即sequence + 1),就会使sequence恢复到0,所以可用sequence==0,表示sequence已满。*/sequence = (sequence + 1) & sequenceMask;//当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生IDif (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {//如果此次生成id的时间戳,与上次的时间戳不同,就已经可以根据时间戳区分id值,sequence从0开始sequence = 0L;}//更新最近一次生成id的时间戳lastTimestamp = timestamp;/* 最核心的二进制位运算操作,生成id1.将41位时间戳左移动22位;2.将5位dataCenterId向左移动17位,并将5位workerId向左移动12位;3.sequence本来就在最低位,因此不需要移动。以下<<和|运算,实际就是将时间戳、机器码和序列号移动到snowflake中相应的位置。最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型*//* 时间戳 2^41 - 1   差不多可以用69年设置一个时间初始值 1288834974657L(1970-01-01 00:00:00到2010年11月04日01:42:54所经过的毫秒数)现在的某一时刻时间戳减去1288834974657L的值,正好在2^41内。*/long twepoch = 1288834974657L;return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;}/*** 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID** @param lastTimestamp* @return*/private long tilNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();/*如果当前时刻的时间戳<=上一次生成id的时间戳,就重新生成当前时间。即确保当前时刻的时间戳,与上一次的时间戳不会重复。*/while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}@Overridepublic void snowFlake() throws Exception {for (int i = 0; i < 22; i++) {long id = nextId();String biStr = Long.toBinaryString(id);System.out.println(id + "\t" + biStr);}}
}

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

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

相关文章

618背后的电商逻辑重构:从价格血战到价值共生

“今年终于没做数学题。” 618进行到一半&#xff0c;行云已经买了很多&#xff0c;大件的有iPad、iWatch&#xff0c;小件的有运动鞋、面膜、纸巾。往年她要凑凑减减&#xff0c;经常要找个店铺凑单&#xff0c;下完单再马上退掉&#xff0c;今年她没废太多脑细胞&#xff0c…

解决 PyTorch 与 Python 3.12 的兼容性问题:`operator torchvision::nms does not exist` 深度解析

解决 PyTorch 与 Python 3.12 的兼容性问题 问题现象错误根源分析终极解决方案🚀 推荐方案:创建 Python 3.11 虚拟环境⚡ 备选方案:使用 PyTorch 夜间构建版(Python 3.12)验证修复技术深度解析最佳实践建议问题现象 当在 Python 3.12 环境中运行以下代码时: from tran…

Git 实战场景

四、标签管理 4.1、标签的理解 在使用 Git 进行版本管理时&#xff0c;**标签&#xff08;Tag&#xff09;**扮演着非常重要的角色。它其实就是对某次提交&#xff08;commit&#xff09;的一个简洁标识&#xff0c;相当于给这次提交起了一个可读、易记的“别名”。比如&…

在同态加密系统中,参与角色以及各角色的功能作用流程图,私钥和公钥分发流程,可能遇到的攻击

一、角色划分与职责 角色身份核心任务密钥权限客户端数据所有者 &#xff08;如医院、用户&#xff09;1. 加密原始数据 2. 上传密文至服务器 3. 接收并解密结果&#xff08;可选&#xff09;持有公钥服务器计算服务提供方 &#xff08;如云平台&#xff09;1. 接收客户端密文…

langchain从入门到精通(六)——LCEL 表达式与 Runnable 可运行协议

1. 多组件 invoke 嵌套的缺点 prompt ChatPromptTemplate.from_template("{query}") llm ChatOpenAI(model"gpt-3.5-turbo-16k") parser StrOutputParser() # 获取输出内容 content parser.invoke( llm.invoke( prompt.invoke( {"query": r…

ArcGIS中批量获取输入面图层A中各要素的四至点的实现方法

一、背景及意义 在日常工作中&#xff0c;我们经常会需要获取面图层的四至点&#xff0c;我们能否在ArcGIS中直接获取面图层的四至点呢&#xff1f;答案是肯定的&#xff0c;请继续往下看。 二、大体思路 使用字段计算器计算输入面图层A中各面要素的XY的最大值和最小值&…

大IPD之——华为的战略本质与实践(二)

华为战略执行的能力如此强&#xff0c;有两个核心原因&#xff1a;一是管理体系起了非常重大的作用&#xff1b;二是企业文化导致华为的执行力特别强。华为在战略方面&#xff0c;为什么每次都能转型成功&#xff1f;背后是有很多实质性的内容支撑的。而华为如何做战略&#xf…

『大模型笔记』第3篇:多长的 Prompt 会阻塞其他请求?优化策略解析

『大模型笔记』多长的 Prompt 会阻塞其他请求?优化策略解析 文章目录 一、更简单的问题:长 Prompt 阻塞请求队列1. 请求并行预填方案(Request-Parallel Prefills)二、根本的问题(Fundamental Flaw):Token 生成被并行预填拖慢1. 解耦预填(Disaggregated Prefill):以延迟优…

21 - GAM模块

论文《Global Attention Mechanism: Retain Information to Enhance Channel-Spatial Interactions》 1、作用 这篇论文提出了全局注意力机制&#xff08;Global Attention Mechanism, GAM&#xff09;&#xff0c;旨在通过保留通道和空间方面的信息来增强跨维度交互&#xf…

Java01--使用IDEA编写运行第一个Java程序HelloWorld

一.先新建一个文件夹存放项目(后续可以推送到Gitee) 二.创建项目 1.打开IDEA&#xff0c;点击首页的新建项目 2.新建空项目并命名&#xff0c;存放路径为步骤一创建的文件夹&#xff1a; 3.在新项目中新建一个src文件夹&#xff08;用于集中管理文件&#xff09; 4.在src文件夹…

目标检测相关【清晰易懂】

目标检测相关 &#xff08;b&#xff09;是语义分割&#xff0c;&#xff08;c&#xff09;是实例分割 目标检测 每个目标一个框标签 实例分割 语义分割 识别每一个目标个体 目标检测基础上进一步提升模型能力有两个方向&#xff1a;实例分割、旋转目标检测。 实例分割 …

强化学习 A2C算法

3.actor-critic方法 3.1 Reinforce 算法&#xff0c;也称为蒙特卡洛策略梯度。蒙特卡洛方差 第一节介绍了DQN 在上一节基于策略的方法中&#xff0c;我们的目标是直接优化策略&#xff0c;而无需使用价值函数。更准确地说&#xff0c;Reinforce 是 基于策略的方法 的一个子类…

关于MCU、MPU、SoC、DSP四大类型芯片

目录 MCU、MPU、SoC、DSP四大类型芯片分析 一、MCU 1、概念 2、特点 3、常见芯片 4、应用场景 二、MPU 1、概念 2、特点 3、常见芯片 4、应用场景 三、SoC 1、概念 2、特点 3、常见芯片 4、应用场景 四、DSP 1、概念 2、特点 3、常见芯片 4、应用场景 MCU、…

【数据结构】图论最短路圣器:Floyd算法如何用双矩阵征服负权图?

最短路径 穿越负权迷雾&#xff1a;Floyd算法如何解锁全图最短路径&#xff1f;​​一、Floyd算法1.1 算法思想1.2 算法逻辑1.3 算法评价1.4 算法限制 二、三种算法对比&#x1f31f;结语 穿越负权迷雾&#xff1a;Floyd算法如何解锁全图最短路径&#xff1f;​​ 大家好&…

宝塔面板集成阿里云 OSS 备份失败的解决方案

宝塔面板集成阿里云OSS备份失败的解决方案 一、问题背景 在使用宝塔面板配置阿里云OSS云存储备份功能时,用户遇到如下错误: Traceback (most recent call last):File "class/CloudStoraUpload.py", line 144, in __init__from alioss_main import OSSClient as ocFile "…

如何安全高效地维护CMS智能插件?

作为网站开发者或运维人员&#xff0c;你是否经历过这样的场景&#xff1a;满怀期待地点击了插件“更新”按钮&#xff0c;刷新页面后却看到一片刺眼的500错误&#xff1f;或发现网站加载速度从2秒骤降到10秒&#xff1f;智能插件为CMS系统&#xff08;如WordPress、Drupal、亿…

FastAPI如何用角色权限让Web应用安全又灵活?

title: FastAPI如何用角色权限让Web应用安全又灵活? date: 2025/06/13 05:46:55 updated: 2025/06/13 05:46:55 author: cmdragon excerpt: 基于角色的路由访问控制是Web应用中常见的安全控制模式,通过为用户分配特定角色来管理权限。FastAPI利用依赖注入系统实现权限控制…

利用 SpreadJS 优化表格渲染性能

引言 在当今的数据驱动时代&#xff0c;表格作为一种重要的数据展示和交互方式&#xff0c;广泛应用于各类 Web 应用中。然而&#xff0c;当表格数据量增大或操作复杂度提高时&#xff0c;渲染性能往往会成为一个关键问题。SpreadJS 作为一款功能强大的纯前端电子表格控件&…

状态检查常用SQL

使用MySQL自身命令获取数据库服务状态。 连接数 -- 最大使用连接数 show status like Max_used_connections; -- 系统配置的最大连接数 show global variables like %max_connections; -- 当前打开的连接数 show status like Threads_connected; 缓存 -- 未从缓冲池读取的次…

【Mac 上离线安装 ADB 工具】

✅ 一、步骤总览&#xff08;离线安装 ADB&#xff09; 下载 ADB 离线包&#xff08;zip 文件&#xff09;解压到一个固定位置&#xff08;比如 ~/adb&#xff09;配置环境变量验证安装是否成功 ✅ 二、步骤详情&#xff08;假设你已经下载好了 zip 文件&#xff09; &#x1…