限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现
文章目录
- 前言
- 1、计数器(固定时间窗口)算法
- 原理
- 代码实现
- 存在的问题
- 2、滑动时间窗口算法
- 原理
- 代码实现
- 存在的问题
- 3、漏桶算法
- 原理
- 代码实现
- 存在的问题
- 4、令牌桶算法
- 原理
- 代码实现
- 最后
本文会对这4个限流算法进行详细说明,并输出实现限流算法的代码示例。
代码是按照自己的理解写的,很简单的实现了功能,还请大佬们多多交流找bug。
下面还有投票,帮忙投个票👍
前言
什么是限流?限流 限流 就是限制流量。在高并发、高流量的场景中我们需要把限流做好,防止突发的流量、恶意的攻击等大量请求的冲击带来不必要的影响,保证业务系统的正常运行。
如何限流?首先我们需要知道限流的基本思路,其次需要知道限流的几种实现方式(这里我们叫限流算法)。
限流的基本思路就是在一个单位时间内流量超过某个阈值后被拒绝或限制。
目前常见的限流算法有计数器(固定时间窗口)算法、滑动时间窗口算法、漏斗算法、令牌算法。
1、计数器(固定时间窗口)算法
计数器(固定时间窗口)算法是最简单的限流算法,实现方式也比较简单。
原理
其原理是:通过维护一个单位时间内的计数值,每当一个请求通过时,就将计数值加1,当计数值超过预先设定的阈值时,就拒绝单位时间内的其他请求。如果单位时间已经结束,则将计数器清零,开启下一轮的计数。
代码实现
import java.util.Random;public class Counter {//时间窗口private final int interval = 1000;//时间窗口内的阈值private final int limit = 5;private long lastTime = System.currentTimeMillis();private int counter = 0;public boolean tryAcquire() {if (System.currentTimeMillis() < lastTime + interval) {// 在时间窗口内counter++;} else {//超过时间窗口充值重置counterlastTime = System.currentTimeMillis();counter = 1;}return counter <= limit;}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();while (true) {if (counter.tryAcquire()) {System.out.println("进行请求");} else {System.out.println("限流了。。。。");}Thread.sleep(100 * new Random().nextInt(5));}}
}
存在的问题
但是这种实现会有一个问题,举个例子:
假设我们设定1s内允许通过的请求阈值是100,如果在时间窗口的最后几毫秒发送了99个请求,紧接着又在下一个时间窗口开始时发送了99个请求,那么这个用户其实在一秒显然超过了阈值但并不会被限流。其实这就是临界值问题,那么临界值问题要怎么解决呢?
2、滑动时间窗口算法
原理
滑动时间窗口算法就是为了解决上述固定时间窗口存在的临界值问题而诞生。要解决这种临界值问题,显然只用一个窗口是解决不了问题的。假设我们仍然设定1秒内允许通过的请求是200个,但是在这里我们需要把1秒的时间分成多格,假设分成5格(格数越多,流量过渡越平滑),每格窗口的时间大小是200毫秒,每过200毫秒,就将窗口向前移动一格。为了便于理解,可以看下图
图中将窗口划为5份,每个小窗口中的数字表示在这个窗口中请求数,所以通过观察上图,可知在当前窗口(200毫秒)只要超过110就会被限流。
代码实现
这里我用了 LinkedList
作为分割窗口,可以快速的实现功能。
import java.util.LinkedList;
import java.util.Random;public class MovingWindow {//时间窗口/msprivate final int interval = 1000;//时间窗口内的阈值private final int limit = 5;//分割窗口个数private int slotCount = 5;private LinkedList<Node> slot = new LinkedList<Node>();public MovingWindow() {new Thread(() -> {while (true) {// 每过200毫秒,就将窗口向前移动一格if (slot.size() == slotCount) {slot.poll();}slot.offer(new Node(System.currentTimeMillis()));try {Thread.sleep(interval / slotCount);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}public boolean tryAcquire() {Node currWindow = getCurrWindow();currWindow.setCount(currWindow.getCount() + 1);return getCounter() <= limit;}private int getCounter() {return slot.stream().mapToInt(Node::getCount).sum();}private Node getCurrWindow() {if (slot.isEmpty()) {while (true) {if (slot.isEmpty()) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}} else break;}}return slot.getLast();}private class Node {private int count;private long time;public Node(long time) {this.time = time;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}public long getTime() {return time;}public void setTime(long time) {this.time = time;}}public static void main(String[] args) throws InterruptedException {MovingWindow counter = new MovingWindow();while (true) {counter.slot.stream().forEach(node -> System.out.print(node.getTime() + ":" + node.getCount() + "|"));if (counter.tryAcquire()) {System.out.println("进行请求");} else {System.out.println("限流了。。。。");}Thread.sleep(100 * new Random().nextInt(5));}}}
存在的问题
那么滑动窗口限流法是完美的吗?细心观察我们应该能马上发现问题,如下图:
0ms-1000ms、200ms-1200ms的请求在我们设置的阈值内,但是100ms-1100ms的请求一共是220,超过了我们所设置的阈值。
滑动时间窗口限流法其实就是计数器算法的一个变种,依然存在临界值的问题。并且流量的过渡是否平滑依赖于我们设置的窗口格数,格数越多,统计越精确,但是具体要分多少格呢?
3、漏桶算法
上面所介绍的两种算法存在流量不能平滑的过渡,下面介绍下漏桶算法。
原理
漏桶算法以一个常量限制了出口流量速率,因此漏桶算法可以平滑突发的流量。其中漏桶作为流量容器我们可以看做一个FIFO的队列,当入口流量速率大于出口流量速率时,因为流量容器是有限的,超出的流量会被丢弃。
下图比较形象的说明了漏桶算法的原理,其中水滴是入口流量,漏桶是流量容器,匀速流出的水是出口流量。
代码实现
这里我用了 ArrayBlockingQueue
作为漏桶,可以快速的实现功能。
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;public class Funnel {//出口流量速率 1s 10个private int rate = 10;//漏桶private ArrayBlockingQueue bucket;public Funnel(int rate, int capacity) {this.rate = rate;this.bucket = new ArrayBlockingQueue(capacity);int speed = 1000 / this.rate;//固定速率滴水new Thread(() -> {while (true) {bucket.poll();try {Thread.sleep(speed);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}public boolean tryAcquire() {// 漏桶里面放水return bucket.offer(this);}public static void main(String[] args) throws InterruptedException {Funnel funnel = new Funnel(10, 100);while (true) {if (funnel.tryAcquire()) {System.out.println("进行请求");} else {System.out.println("限流了。。。。");}Thread.sleep(20 * new Random().nextInt(5));}}}
存在的问题
因为漏桶算法的流出速率是固定的,所以漏桶算法不支持出现突发流出流量。但是在实际情况下,流量往往是突发的。
4、令牌桶算法
令牌桶算法是漏桶算法的改进版,可以支持突发流量。不过与漏桶算法不同的是,令牌桶算法的漏桶中存放的是令牌而不是流量。
原理
令牌桶算法是如何支持突发流量的呢?最开始,令牌桶是空的,我们以恒定速率往令牌桶里加入令牌,令牌桶被装满时,多余的令牌会被丢弃。当请求到来时,会先尝试从令牌桶获取令牌(相当于从令牌桶移除一个令牌),获取成功则请求被放行,获取失败则阻塞或拒绝请求。那么当突发流量来临时,只要令牌桶有足够的令牌,就不会被限流。
代码实现
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;public class Token {//添加令牌速率 1s 10个private int rate = 10;//漏桶private ArrayBlockingQueue bucket;public Token(int rate, int capacity) {this.rate = rate;this.bucket = new ArrayBlockingQueue(capacity);//恒定速率往漏桶里面添加令牌int speed = 1000 / this.rate;new Thread(() -> {while (true) {bucket.offer(this);try {Thread.sleep(speed);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}public boolean tryAcquire() {// 漏桶里面取令牌return null != bucket.poll();}public static void main(String[] args) throws InterruptedException {Token funnel = new Token(10, 100);//8s后突发流量Thread.sleep(8000);while (true) {if (funnel.tryAcquire()) {System.out.println("进行请求");} else {System.out.println("限流了。。。。");}Thread.sleep(20 * new Random().nextInt(5));}}}
最后
或许大家在工作中会用现成的一些限流组件比如:Spring Cloud 的 Hystrix 、Spring Cloud Alibaba 的 Sentinel 或者是 Google 的 Guava 限流器。其实现原理离不开上述所说的4种限流算法,我们开发人员还是要知其然,知其所以然。
相关文章:

限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现
文章目录前言1、计数器(固定时间窗口)算法原理代码实现存在的问题2、滑动时间窗口算法原理代码实现存在的问题3、漏桶算法原理代码实现存在的问题4、令牌桶算法原理代码实现最后本文会对这4个限流算法进行详细说明,并输出实现限流算法的代码示…...

C++回溯算法---图的m着色问题01
C回溯算法---图的m着色问题 图的m着色问题是指给定一个图以及m种不同的颜色,尝试将每个节点涂上其中一种颜色,使得相邻的节点颜色不相同。这个问题可以转化为在解空间树中寻找可行解的问题,其中每个分支结点都有m个儿子结点,最底层…...

ESP32 分区表
ESP32 分区表 1. 分区表概述 ESP32 针对 flash 进行划分,划分为不同的区域用作不同的功能,并在flash的 0x8000 位置处烧写了一张分区表用来描述分区信息。 分区表可以根据自己的需要进行配置,每一个分区都有其特定的作用,可根据…...
JJJ-2 init_IRQ
void __init init_IRQ(void) {int ret;if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)irqchip_init();else // init_irq成员定义为imx6ul_init_irq,会走这个分支machine_desc->init_irq(); if (IS_ENABLED(CONFIG_OF) && IS_ENABLED…...

【NLP实战】基于Bert和双向LSTM的情感分类【下篇】
文章目录前言简介第一部分关于pytorch lightning保存模型的机制关于如何读取保存好的模型完善测试代码第二部分第一次训练出的模型的过拟合问题如何解决过拟合后记前言 本文涉及的代码全由博主自己完成,可以随意拿去做参考。如对代码有不懂的地方请联系博主。 博主…...

程序设计方法学
体育竞技分析 问题分析 体育竞技分析 需求:毫厘是多少? 如何科学分析体育竞技比赛? 输入:球员的水平 输出:可预测的比赛成绩 体育竞技分析:模拟N场比赛 计算思维:抽象 自动化 模拟&am…...

Hadoop之Yarn篇
目录 编辑 Yarn的工作机制: 全流程作业: Yarn的调度器与调度算法: FIFO调度器(先进先出): 容量调度器(Capacity Scheduler): 容量调度器资源分配算法࿱…...

Spring Cloud Nacos使用总结
目录 安装Nacos服务器 服务发现与消费 服务发现与消费-添加依赖 服务发现-配置文件 服务发现-注解 服务发现-Controller 服务消费-配置文件 服务消费-注解与Ribbon消费代码 服务消费-运行 配置管理 配置管理-添加依赖 配置管理-配置文件 配置管理-注解 配置管理-…...

目标检测框架yolov5环境搭建
目前,目标检测框架中,yolov5 是很火的,它基于pytorch框架,集成opencv等框架,项目地址:https://github.com/ultralytics/yolov5,对我来说,机器学习、深度学习才开始接触,本…...

Vulnhub:Digitalworld.local (JOY)靶机
kali:192.168.111.111 靶机:192.168.111.130 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --scripthttp-enum 192.168.111.130 使用enum4linux枚举目标smb服务,发现两个系统用户 enum4linux -a 192.168.111.130 ftp可以匿名登陆ÿ…...

STL源码剖析-六大部件, 部件的关系,复杂度, 区间表示
C标准库-体系结构与内核分析 根据源代码来分析 介绍 自学C侯捷老师的STL源码剖析的个人笔记,方便以后进行学习,查询。 为什么要学STL?按侯捷老师的话来说就是:使用一个东西,却不明白它的道理,不高明&…...
总有一个可用的连接,metaIPC1.2进入智能连接新时代
概述 metaIPC有1.0和2.0两个产品系列,2.0版本是可视对讲IPC,1.0新版本1.2在全面兼容ICE规范基础上进行了扩展,使metaIPC1.2进入智能化连接新时代。 metaIPC1.2在host/stun/turn/srs/zlm/janus/freeswitch等p2p/sfu/mcu进行全方位连通测试&a…...
棋盘问题c
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。 Input …...
华纳云:Linux系统下怎么创建普通用户并更改用户组
本篇内容主要讲解“Linux系统下怎么创建普通用户并更改用户组”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Linux系统下怎么创建普通用户并更改用户组”吧! 要求 项目做权限管理,不用root部…...

「她时代」背后的欧拉力量
2018年大热电视剧《北京女子图鉴》,讲述了一群在北京打拼的职业女性,她们背井离乡,被现实包裹,被压力、责任困扰,但依旧用倔强的个性、不屈的进取心和深厚的知识技能努力营造、交织出一片励志的天空,既激昂…...
kubespray v2.21.0 在线部署 kubernetes v1.24.0 集群【2】
文章目录创建 虚拟机模板虚拟机名称配置静态地址配置代理yum 配置配置主机名安装 git安装 docker安装 ansible配置内核参数安装 k8s定制安装新增节点配置主机名配置代理配置互信更新 inventory报错kubespray v2.21.0 部署 kubernetes v1.24.0 集群 【1】在 Rocky linux 8.7 使用…...

聚焦运营商信创运维,美信时代监控易四大亮点值得一试!
2021年11月《“十四五”信息通信行业发展规划》提出,到2025年,我国将建立高速泛在、集成互联、智能绿色、安全可靠的新型数字基础设施体系。 此《规划》让我国运营商信创进一步加速,中国移动、中国电信、中国联通等都先后加入信创大军&#x…...
[python刷题模板] 博弈入门-记忆化搜索/dp/打表
[python刷题模板] 博弈入门-记忆化搜索/dp/打表 一、 算法&数据结构1. 描述2. 复杂度分析3. 常见应用4. 常用优化二、 模板代码1. 打表贪心的博弈2. 464. 我能赢吗3. Nim游戏--最最基础版n1。三、其他四、更多例题五、参考链接一、 算法&数据结构 1. 描述 博弈一直没…...

I2C通信
一、理论上了解I2C时序 I2C写数据时序如图: 通过解析器解析I2C通信如上图(SCL和SDA反了)。 1---起始信号 2、3---应答信号ACK 5---停止信号 起始信号:SCL线是高电平时,SDA线从高电平向低电平切换。 停…...

【Linux】man什么都搜不了,No manual entry for xxx的解决方案
本文首发于 慕雪的寒舍 man什么都搜不了,No manual entry for xxx的解决方案 系统 CentOS 7.6 1.问题描述 今天查手册的时候,发现man什么都查不了。不管是系统接口还是函数,都显示没有入口文档(No manual entry for)…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...