限流,流量整形算法
写在前面
源码 。
本文看下流量整形相关算法。
目前流量整形算法主要有三种,计数器,漏桶,令牌桶。分别看下咯!
1:计数器
1.1:描述
单位时间内只允许指定数量的请求,如果是时间区间内超过指定数量,则直接拒绝,如果时间区间结束,则重置计数器,开始下一个时间区间。

1.2:程序
package com.dahuyou.algrithm.triffic.shaper.counter;import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;// 计速器 限速
public class CounterLimiter {// 起始时间private static long startTime = System.currentTimeMillis();// 时间区间的时间间隔 msprivate static long interval = 1000;// 每interval时间内限制数量private static long maxCount = 2;//累加器private static AtomicLong accumulator = new AtomicLong();// 计数判断, 是否超出限制private static long tryAcquire(long taskId, int turn) {long nowTime = System.currentTimeMillis();//在时间区间之内if (nowTime < startTime + interval) {long count = accumulator.incrementAndGet();if (count <= maxCount) {System.out.println("taskId: " + taskId + " 正常执行!");return count;} else {// 返回-1说明时间区间内被限制了
// return -count;System.out.println("时区内达到次数咯!");return -1;}} else {//在时间区间之外synchronized (CounterLimiter.class) {System.out.println("新时间区到了,taskId:" + taskId + ", turn {}.." + turn);// 再一次判断,防止重复初始化if (nowTime > startTime + interval) {accumulator.set(0);startTime = nowTime;}}return 0;}}final int threads = 1;//线程池,用于多线程模拟测试
// private ExecutorService pool = Executors.newFixedThreadPool(10);private ExecutorService pool = Executors.newFixedThreadPool(threads);@Testpublic void testLimit() {// 被限制的次数AtomicInteger limited = new AtomicInteger(0);// 线程数
// final int threads = 2;// 每条线程的执行轮数final int turns = 20;// 同步器CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() ->{try {for (int j = 0; j < turns; j++) {long taskId = Thread.currentThread().getId();long index = tryAcquire(taskId, j);
// if (index <= 0) {if (index == -1) {// 被限制的次数累积limited.getAndIncrement();}Thread.sleep(200);}} catch (Exception e) {e.printStackTrace();}//等待所有线程结束countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}float time = (System.currentTimeMillis() - start) / 1000F;//输出统计结果System.out.println("限制的次数为:" + limited.get() +",通过的次数为:" + (threads * turns - limited.get()));System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns));System.out.println("运行的时长为:" + time);}}
输出:

1.3:优缺点
- 优点
简单
- 缺点
无法处理流量分配不均匀的情况,可能导致大量的请求被拒绝
1.4:适用场景
流量比较平稳业务场景。比如我司的机器人外呼业务,因为是程序在跑,所以流量很稳定,一旦业务配置导致流量增高,则可以使用该算法进行限流。
但对于突发流量场景,可能会因为很短时间内的突发流量就导致计数器达到最大值,从而时间区间内的剩余时间所有请求全部丢弃,这也存在着被攻击的风险。

2:漏桶
2.1:描述
水(对应请求)从进水口进入到漏桶里,漏桶以一定的速度出水(请求放行),当水流入速度过大,桶内的总水量大于桶容量会直接溢出,请求被拒绝,如图所示:

2.2:程序
package com.dahuyou.algrithm.triffic.shaper.counter;import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;// 漏桶 限流
public class LeakBucketLimiter {// 计算的起始时间private static long lastOutTime = System.currentTimeMillis();// 流出速率 每100毫秒漏2次private static int leakRate = 1;
// private static int leakRate = 2000;// 桶的容量private static int capacity = 5;//剩余的水量private static AtomicInteger water = new AtomicInteger(0);//返回值说明:// false 没有被限制到// true 被限流public static synchronized boolean isLimit(long taskId, int turn) {// 如果是空桶,就当前时间作为漏出的时间if (water.get() == 0) {lastOutTime = System.currentTimeMillis();water.addAndGet(1);return false;}// 执行漏水
// int waterLeaked = ((int) ((System.currentTimeMillis() - lastOutTime) / 1000)) * leakRate;int waterLeaked = ((int) ((System.currentTimeMillis() - lastOutTime) / 100)) * leakRate;// 计算剩余水量,当前的量减去漏出去的量就是剩余的量int waterLeft = water.get() - waterLeaked;// 要注意:剩余的量最小是0water.set(Math.max(0, waterLeft));// 重新更新leakTimeStamplastOutTime = System.currentTimeMillis();// 尝试加水,并且水还未满 ,放行if ((water.get()) < capacity) {System.out.println("水未满,成功加水");water.addAndGet(1);return false;} else {System.out.println("水已满,水溢出");// 水满,拒绝加水, 限流return true;}}final int threads = 1;//线程池,用于多线程模拟测试(负责加水)private ExecutorService pool = Executors.newFixedThreadPool(threads);private ExecutorService outWaterPool = Executors.newFixedThreadPool(threads);@Testpublic void testLimit() {// new Thread(() -> {
// for (int i = 0; i < 1000; i++) {
// if (water.get() > 0) {
// System.out.println("出水了");
// water.decrementAndGet();
// } else {
// System.out.println("无水可出了");
// }
// try {
// TimeUnit.MILLISECONDS.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }).start();// 被限制的次数AtomicInteger limited = new AtomicInteger(0);// 线程数
// final int threads = 2;// 每条线程的执行轮数final int turns = 20;// 线程同步器CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() ->{try {for (int j = 0; j < turns; j++) {long taskId = Thread.currentThread().getId();boolean intercepted = isLimit(taskId, j);if (intercepted) {// 被限制的次数累积limited.getAndIncrement();}Thread.sleep(200);}} catch (Exception e) {e.printStackTrace();}//等待所有线程结束countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}float time = (System.currentTimeMillis() - start) / 1000F;//输出统计结果System.out.println("限制的次数为:" + limited.get() +",通过的次数为:" + (threads * turns - limited.get()));System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns));System.out.println("运行的时长为:" + time);}
}
运行:
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
限制的次数为:0,通过的次数为:20
限制的比例为:0.0
运行的时长为:4.136Process finished with exit code 0
此时因为水流出的速度快于流入的速度,所以,一直可以成功加水,可以修改leakRate=0,再运行:
水未满,成功加水
水未满,成功加水
水未满,成功加水
水未满,成功加水
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
水已满,水溢出
限制的次数为:15,通过的次数为:5
限制的比例为:0.75
运行的时长为:4.176Process finished with exit code 0
就可以看到水满溢出的情况了。
2.3:优缺点
- 优点
可应对突发流量,避免服务被冲垮,从而起到保护服务的作用
- 缺点
因为出口速率固定,所以当服务能力提升时,无法自动匹配后端服务的能力提升
2.4:适用场景
3:令牌桶
3.1:描述
有一个固定容量的令牌桶,按照一定的速率(可以调节)向令牌桶中放入令牌,请求想要被执行,必须能够从令牌桶中获取到令牌,否则将会被抛弃,参考下图:

3.2:程序
package com.dahuyou.algrithm.triffic.shaper.counter;//import lombok.extern.slf4j.Slf4j;import org.junit.Test;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;// 令牌桶 限速
//@Slf4j
public class TokenBucketLimiter {// 上一次令牌发放时间public long lastTime = System.currentTimeMillis();// 桶的容量public int capacity = 2;// 令牌生成速度 /s,如果是调大令牌的生成速度,则服务能力也会得到提高(在服务扛得住的前提下)public int rate = 2;// 当前令牌数量public AtomicInteger tokens = new AtomicInteger(0);//返回值说明:// false 没有被限制到// true 被限流public synchronized boolean isLimited(long taskId, int applyCount) {long now = System.currentTimeMillis();//时间间隔,单位为 mslong gap = now - lastTime;//计算时间段内的令牌数int reverse_permits = (int) (gap * rate / 1000);int all_permits = tokens.get() + reverse_permits;// 当前令牌数(固有的令牌加上时间段内新产生的令牌就是当前真实的令牌数啦),// 因为令牌桶也有固定的数量所以要取下最小值tokens.set(Math.min(capacity, all_permits));
// log.info("tokens {} capacity {} gap {} ", tokens, capacity, gap);
// System.out.println("tokens " + tokens + " capacity " + capacity + " gap " + gap);/*** 如果申请的数量大于可用令牌数,则拒绝,否则发放令牌,执行请求*/if (tokens.get() < applyCount) {System.out.println("没有辣么多令牌啦!!!");// 若拿不到令牌,则拒绝// log.info("被限流了.." + taskId + ", applyCount: " + applyCount);return true;} else {System.out.println("令牌拿去撒!!!");// 还有令牌,领取令牌tokens.getAndAdd( - applyCount);lastTime = now;// log.info("剩余令牌.." + tokens);return false;}}//线程池,用于多线程模拟测试private ExecutorService pool = Executors.newFixedThreadPool(10);@Testpublic void testLimit() {// 被限制的次数AtomicInteger limited = new AtomicInteger(0);// 线程数final int threads = 2;// 每条线程的执行轮数final int turns = 20;// 同步器CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() ->{try {for (int j = 0; j < turns; j++) {long taskId = Thread.currentThread().getId();boolean intercepted = isLimited(taskId, 1);if (intercepted) {// 被限制的次数累积limited.getAndIncrement();}Thread.sleep(200);}} catch (Exception e) {e.printStackTrace();}//等待所有线程结束countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}float time = (System.currentTimeMillis() - start) / 1000F;//输出统计结果System.out.println("限制的次数为:" + limited.get() +",通过的次数为:" + (threads * turns - limited.get()));System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns));System.out.println("运行的时长为:" + time);}
}
输出:

展示的是既有申请到令牌也有没有申请到令牌的场景,修改代码public int rate = 2000;给令牌发放一个非常大的速度,此时就会一直可以拿得到令牌:

修改程序public int rate = 0;直接不发放令牌,就可以看到令牌全部申请失败的场景:

3.3:优缺点
- 优点
1:因为令牌桶容量有限制,所以可以应对突发流量
2:服务QPS增加或者降低时只需要对应调整令牌的发放速度即可适配
- 缺点
3.4:适用场景
写在后面
参考文章列表
限流:计数器、漏桶、令牌桶 三大算法的原理与实战(史上最全) 。
相关文章:
限流,流量整形算法
写在前面 源码 。 本文看下流量整形相关算法。 目前流量整形算法主要有三种,计数器,漏桶,令牌桶。分别看下咯! 1:计数器 1.1:描述 单位时间内只允许指定数量的请求,如果是时间区间内超过指…...
【C++知识扫盲】------C++ 中的引用入门
在 C 中,引用(reference) 是一个非常重要的概念,它提供了一种别名机制,让我们可以给已经存在的变量起一个新的名字,并且能够通过这个别名直接操作原始变量。本文将详细介绍引用的定义、使用场景及其与指针的…...
【机器学习】6 ——最大熵模型
机器学习6——最大熵模型 目录 机器学习6——最大熵模型最大熵(maximum entropy)模型模型模型学习(估计参数)模型评价应用 最大熵(maximum entropy)模型 选择熵最大的概率模型 熵是衡量不确定性的…...
小程序——生命周期
文章目录 运行机制更新机制生命周期介绍应用级别生命周期页面级别生命周期组件生命周期生命周期两个细节补充说明总结 运行机制 用一张图简要概述一下小程序的运行机制 冷启动与热启动: 小程序启动可以分为两种情况,一种是冷启动,一种是热…...
基于微信小程序的宠物之家的设计与实现
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的宠物之家/宠物综合…...
自定义EPICS在LabVIEW中的测试
继续上一篇:LabVIEW中EPICS客户端/服务端的测试 变量定义 You can use CaLabSoftIOC.vi to create new EPICS variables and start them. CA Lab - LabVIEW (Realtime) EPICS INPUT: PV set Cluster-array of names, data types and field definitions to crea…...
基于深度学习的农作物病害检测
基于深度学习的农作物病害检测利用卷积神经网络(CNN)、生成对抗网络(GAN)、Transformer等深度学习技术,自动识别和分类农作物的病害,帮助农业工作者提高作物管理效率、减少损失。 1. 农作物病害检测的挑战…...
【C#】命名规范
文章目录 C# 命名规范使用Pascal case使用Camel case方法、属性、类命名见名知义LINQ查询变量使用有意义的名称如何声明成员变量和字段正确格式化和缩进代码如何撰写备注 通用C#编码最佳实践如何将值与空字符串进行比较使用异常处理使用&&和||可获得更好的性能单一职责…...
超级帐本(Hyperledger)
1. Hyperledger 项目 Hyperledger 下有两类项目:第一类是区块链框架项目;第二类是支持这些区块链的相关工具或模块。 在 Hyperledger 框架下,目前有 5 个区块链框架项目:Fabric、Sawtooth Lake、Iroha、Burrow 和 Indy。 在模块类下,则有 Hyp…...
如何精细优化网站关键词排名:实战经验分享
在数字营销日益激烈的今天,我深知每一个关键词的排名都关乎着网站的流量与转化。凭借多年的实战经验,我深刻体会到,要想在浩如烟海的网络世界中脱颖而出,精细化的关键词优化策略至关重要。今天,我将从实战角度出发&…...
Ruoyi Cloud 本地启动
本文视频版本:https://www.bilibili.com/video/BV1SNtueBE9M 参考 http://doc.ruoyi.vip/ https://gitee.com/y_project/RuoYi-Cloud https://blog.csdn.net/cs_dnzk/article/details/135289966 https://doc.ruoyi.vip/ruoyi-cloud/cloud/seata.html#%E5%9F%BA%E6…...
Nginx解析:入门笔记
🌈 个人主页:danci_ 🔥 系列专栏:《设计模式》《MYSQL》 💪🏻 制定明确可量化的目标,坚持默默的做事。 ✨欢迎加入探索nginx之旅✨ 👋 大家好!文本学习和探索Nginx配置。…...
在 Mac 上安装双系统会影响性能吗,安装双系统会清除数据吗?
在 Mac 系统安装并使用双系统已经成为了许多用户办公的选择之一,双系统可以让用户在 Mac 上同时运行 Windows 或其他操作系统。然而,许多用户担心这样做会对 Mac 的性能产生影响。 接下来将给大家介绍 Mac 装双系统会影响性能吗,Mac装双系统…...
vue3提交按钮限制重复点击
下载lodash npm install lodash 引入并使用 <template><div click"submit()">提交</div> </template><script setup>import { debounce } from lodash;const submit debounce(() > {//业务代码},2000,{leading: true,trailing:…...
Java | Leetcode Java题解之第395题至少有K个重复字符的最长子串
题目: 题解: class Solution {public int longestSubstring(String s, int k) {int ret 0;int n s.length();for (int t 1; t < 26; t) {int l 0, r 0;int[] cnt new int[26];int tot 0;int less 0;while (r < n) {cnt[s.charAt(r) - a];…...
20240915 每日AI必读资讯
国家网信办发布《人工智能生成合成内容标识办法(征求意见稿)》 - 要求所有的AI生成内容都要打标,包括文字、图像、视频、音频… - 文本内容要插入标识符提醒,音频内容要在里面插入提示音 - 对创作者不太友好,对平台…...
量化交易需要注意的关于股票交易挂单排队规则的问题
炒股自动化:申请官方API接口,散户也可以 python炒股自动化(0),申请券商API接口 python炒股自动化(1),量化交易接口区别 Python炒股自动化(2):获取…...
应急响应实战---是谁修改了我的密码?
前言:此次应急响应为真实案例,客户反馈无法通过密码登录服务器,疑似服务器被入侵 0x01 如何找回密码? 客户服务器为windows server2019,运维平台为PVE平台;实际上无论是windows系统或者是linux系统&#…...
知识的通用性
概述 很久没有写文章了,因为集团公司当前在大刀阔斧的改革,人员精简,很多事情都合并到同一个人身上,同时将内部的沟通软件平台又做一次大的切换,很多资料都需要重新的整理。 所以,抱歉,很多内…...
36岁,大厂女程序员,中年失业后,我开始接受自己的平凡,并深耕自己
作为80后秦岭大山里面的穷苦农民工家的孩子,从小因为讨厌做家务,做农活,而且家里孩子众多,物质匮乏,从小就特别渴望走出大山。 上学的时候,通过刻苦努力,成绩也还算可以,经常受到老师…...
开源智能体技术解析:从LangChain到自主抓取,构建自动化工作流
1. 项目概述:从“Awesome”列表看开源智能体生态的演进 最近在梳理一些前沿的自动化工具链时,又翻到了 mergisi/awesome-openclaw-agents 这个仓库。对于长期关注AI Agent(智能体)和自动化工作流开发的同行来说,这类…...
【ZYNQ】AXI4总线协议实战:从握手时序到PS-PL高效通信
1. AXI4总线协议基础:从握手信号到通道架构 第一次接触ZYNQ的PS-PL通信时,我被AXI4协议里那些VALID/READY信号搞得头晕眼花。直到在示波器上抓到真实的握手波形,才突然理解这个看似复杂的协议其实像极了我们日常的对话机制——只有当说话方准…...
高考解析几何“秒杀”技巧:用极点极线快速搞定椭圆定点定值难题
高考解析几何“秒杀”技巧:用极点极线快速搞定椭圆定点定值难题 解析几何作为高考数学的压轴题型,常常让考生望而生畏。面对复杂的计算和抽象的条件,如何在有限时间内快速找到突破口?极点极线理论作为高等几何中的重要工具&#x…...
ViGEmBus终极指南:Windows游戏控制器模拟驱动完全解析
ViGEmBus终极指南:Windows游戏控制器模拟驱动完全解析 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus ViGEmBus是一款运行在Windows内核模式的驱…...
实战指南:用UABEA高效解析Unity资源结构的5个关键要点
实战指南:用UABEA高效解析Unity资源结构的5个关键要点 【免费下载链接】UABEA c# uabe for newer versions of unity 项目地址: https://gitcode.com/gh_mirrors/ua/UABEA 在Unity开发的世界里,资源管理往往是项目优化中最棘手的一环。你是否曾经…...
Gopeed下载器深度解析:从零开始构建你的全平台高速下载解决方案
Gopeed下载器深度解析:从零开始构建你的全平台高速下载解决方案 【免费下载链接】gopeed A fast, modern download manager for HTTP, BitTorrent, Magnet, and ed2k. Cross-platform, built with Golang and Flutter. 项目地址: https://gitcode.com/GitHub_Tre…...
从零构建Go Web框架:解析the0极简框架的设计原理与实现
1. 项目概述:一个极简主义Web框架的诞生在Web开发的世界里,我们常常面临一个选择:是拥抱功能齐全但略显臃肿的“巨无霸”框架,还是追求极致轻量与灵活的自定义方案?对于许多追求性能、热爱掌控感,或是需要构…...
LLVM开发实战指南:从入门到精通编译器与程序分析
1. 项目概述:为什么你需要一份LLVM指南?如果你是一名C开发者,或者对编译器、程序分析、代码优化这些底层技术感兴趣,那么“LLVM”这个名字对你来说一定不陌生。它早已不是象牙塔里的学术玩具,而是驱动着从iOS、macOS到…...
别再手动调色了!用Matlab bar3函数一键生成论文级渐变三维柱状图(附完整代码)
别再手动调色了!用Matlab bar3函数一键生成论文级渐变三维柱状图(附完整代码) 科研图表的美观程度直接影响论文的第一印象,而三维柱状图在展示多维度数据时尤为常见。传统手动调整每个柱体的颜色、透明度、光照效果不仅耗时&#…...
ARM Debug Interface v5.1架构解析与调试实践
1. ARM Debug Interface v5.1架构深度解析1.1 调试接口技术演进与核心价值ARM调试接口(ADI)技术历经多次迭代,v5.1版本作为当前主流标准,在嵌入式系统调试领域确立了关键地位。调试接口本质上是处理器核与外部调试工具之间的标准化通信桥梁,其…...
