数据结构和算法专题---4、限流算法与应用
本章我们会对限流算法做个简单介绍,包括常用的限流算法(计数器、漏桶算法、令牌桶案发、滑动窗口)的概述、实现方式、典型场景做个说明。
什么是限流算法
限流是对系统的一种保护措施。即限制流量请求的频率(每秒处理多少个请求)。一般来说,当请求流量超过系统的瓶颈,则丢弃掉多余的请求流量,保证系统的可用性。即要么不放进来,放进来的就保证提供服务。
计数器
概述
计数器采用简单的计数操作,到一段时间节点后自动清零
实现
package com.ls.cloud.sys.alg.limit;import com.ls.cloud.common.core.util.DateUtil;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;public class Counter {public static void main(String[] args) {//计数器,这里用信号量实现final Semaphore semaphore = new Semaphore(1);//定时器,到点清零ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {semaphore.release(1);}},3000,3000,TimeUnit.MILLISECONDS);//模拟无限请求从天而降降临while (true) {try {//判断计数器semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}//如果准许响应,打印一个okDate date = new Date();SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");System.out.println("执行------------"+ dateFormat.format(date));}}
}
结果分析
执行------------2023-12-05 02:17:33
执行------------2023-12-05 02:17:36
执行------------2023-12-05 02:17:39
执行------------2023-12-05 02:17:42
执行------------2023-12-05 02:17:45
优缺点
- 优点:实现起来非常简单。
- 缺点:控制力度太过于简略,假如1s内限制3次,那么如果3次在前100ms内已经用完,后面的900ms将只能处于阻塞状态,白白浪费掉
典型场景
使用计数器限流的场景较少,因为它的处理逻辑不够灵活。最常见的是登录验证码倒计时,60秒接收一次,如果在限流场景使用计数器,可能导致前面100ms进入全部流程,系统可能依然会出现宕机的情况。
漏桶算法
概述
漏桶算法将请求缓存在桶中,服务流程匀速处理。超出桶容量的部分丢弃。漏桶算法主要用于保护内部的处理业务,保障其稳定有节奏的处理请求,但是无法根据流量的波动弹性调整响应能力。现实中,类似容纳人数有限的服务大厅开启了固定的服务窗口。
实现
可以基于队列进行实现。
package com.ls.cloud.sys.alg.limit;import java.util.concurrent.*;public class Barrel {public static void main(String[] args) {//桶,用阻塞队列实现,容量为3final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue(3);//定时器,相当于服务的窗口,2s处理一个ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {// 删除队首元素int v = que.poll();System.out.println("处理:"+v);}},2000,2000,TimeUnit.MILLISECONDS);//无数个请求,i 可以理解为请求的编号int i=0;while (true) {i++;try {System.out.println("put:"+i);//如果是put,会一直等待桶中有空闲位置,不会丢弃
// que.put(i);//等待1s如果进不了桶,就溢出丢弃que.offer(i,1000,TimeUnit.MILLISECONDS);} catch (Exception e) {e.printStackTrace();}}}}
结果
put:1
put:2
put:3
put:4
put:5
处理:1
put:6
put:7
处理:2
put:8
put:9
处理:3
put:10
put:11
处理:5
put:12
put:13
- put任务号按照顺序入桶
- 执行任务匀速的2s一个被处理
- 因为桶的容量只有3,所以1-3完美执行,4被溢出丢弃,5正常执行
优缺点
- 优点:有效的挡住了外部的请求,保护了内部的服务不会过载
- 内部服务匀速执行,无法应对流量洪峰,无法做到弹性处理突发任务
- 任务超时溢出时被丢弃。现实中可能需要缓存队列辅助保持一段时间
典型场景
nginx中的限流是漏桶算法的典型应用,配置案例如下:
http { #$binary_remote_addr 表示通过remote_addr这个标识来做key,也就是限制同一客户端ip地址。 #zone=one:10m 表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。 #rate=1r/s 表示允许相同标识的客户端每秒1次访问 limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { location /limited/ { #zone=one 与上面limit_req_zone 里的name对应。 #burst=5 缓冲区,超过了访问频次限制的请求可以先放到这个缓冲区内,类似代码中的队列长度。#nodelay 如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队,类似代码中的put还是offer。 limit_req zone=one burst=5 nodelay; }}
令牌桶
概述
令牌桶算法可以认为是漏桶算法的一种升级,它不但可以将流量做一步限制,还可以解决漏桶中无法弹性伸缩处理请求的问题。体现在现实中,类似服务大厅的门口设置门禁卡发放。发放是匀速的,请求较少时,令牌可以缓存起来,供流量爆发时一次性批量获取使用。而内部服务窗口不设限。
实现
package com.ls.cloud.sys.alg.limit;import java.util.concurrent.*;public class Token {public static void main(String[] args) throws InterruptedException {//令牌桶,信号量实现,容量为3final Semaphore semaphore = new Semaphore(3);//定时器,1s一个,匀速颁发令牌ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {if (semaphore.availablePermits() < 3){semaphore.release();}
// System.out.println("令牌数:"+semaphore.availablePermits());}},1000,1000,TimeUnit.MILLISECONDS);//等待,等候令牌桶储存Thread.sleep(5);//模拟洪峰5个请求,前3个迅速响应,后两个排队for (int i = 0; i < 5; i++) {semaphore.acquire();System.out.println("洪峰:"+i);}//模拟日常请求,2s一个for (int i = 0; i < 3; i++) {Thread.sleep(1000);semaphore.acquire();System.out.println("日常:"+i);Thread.sleep(1000);}//再次洪峰for (int i = 0; i < 5; i++) {semaphore.acquire();System.out.println("洪峰:"+i);}//检查令牌桶的数量for (int i = 0; i < 5; i++) {Thread.sleep(2000);System.out.println("令牌剩余:"+semaphore.availablePermits());}}
}
结果
洪峰:0
洪峰:1
洪峰:2
洪峰:3
洪峰:4
日常:0
日常:1
日常:2
洪峰:0
洪峰:1
洪峰:2
洪峰:3
洪峰:4
令牌剩余:2
令牌剩余:3
令牌剩余:3
令牌剩余:3
令牌剩余:3
- 洪峰0-2迅速被执行,说明桶中暂存了3个令牌,有效应对了洪峰
- 洪峰3,4被间隔性执行,得到了有效的限流
- 日常请求被匀速执行,间隔均匀
- 第二波洪峰来临,和第一次一样
- 请求过去后,令牌最终被均匀颁发,积累到3个后不再上升
典型场景
springcloud中gateway可以配置令牌桶实现限流控制,案例如下:
cloud: gateway: routes: ‐ id: limit_route uri: http://localhost:8080/test filters: ‐ name: RequestRateLimiter args: #限流的key,ipKeyResolver为spring中托管的Bean,需要扩展KeyResolver接口 key‐resolver: '#{@ipResolver}' #令牌桶每秒填充平均速率,相当于代码中的发放频率 redis‐rate‐limiter.replenishRate: 1 #令牌桶总容量,相当于代码中,信号量的容量 redis‐rate‐limiter.burstCapacity: 3
滑动窗口
概述
滑动窗口可以理解为细分之后的计数器,计数器粗暴的限定1分钟内的访问次数,而滑动窗口限流将1分钟拆为多个段,不但要求整个1分钟内请求数小于上限,而且要求每个片段请求数也要小于上限。相当于将原来的计数周期做了多个片段拆分,更为精细。
实现
package com.ls.cloud.sys.alg.limit;import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class Window {//整个窗口的流量上限,超出会被限流final int totalMax = 5;//每片的流量上限,超出同样会被拒绝,可以设置不同的值final int sliceMax = 5;//分多少片final int slice = 3;//窗口,分3段,每段1s,也就是总长度3sfinal LinkedList<Long> linkedList = new LinkedList<>();//计数器,每片一个key,可以使用HashMap,这里为了控制台保持有序性和可读性,采用TreeMapMap<Long,AtomicInteger> map = new TreeMap();//心跳,每1s跳动1次,滑动窗口向前滑动一步,实际业务中可能需要手动控制滑动窗口的时机。ScheduledExecutorService service = Executors.newScheduledThreadPool(1);//获取key值,这里即是时间戳(秒)private Long getKey(){return System.currentTimeMillis()/1000;}public Window(){//初始化窗口,当前时间指向的是最末端,前两片其实是过去的2sLong key = getKey();for (int i = 0; i < slice; i++) {linkedList.addFirst(key-i);map.put(key-i,new AtomicInteger(0));}//启动心跳任务,窗口根据时间,自动向前滑动,每秒1步service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {Long key = getKey();//队尾添加最新的片linkedList.addLast(key);map.put(key,new AtomicInteger());//将最老的片移除map.remove(linkedList.getFirst());linkedList.removeFirst();System.out.println("step:"+key+":"+map);;}},1000,1000,TimeUnit.MILLISECONDS);}//检查当前时间所在的片是否达到上限public boolean checkCurrentSlice(){long key = getKey();AtomicInteger integer = map.get(key);if (integer != null){return integer.get() < totalMax;}//默认允许访问return true;}//检查整个窗口所有片的计数之和是否达到上限public boolean checkAllCount(){return map.values().stream().mapToInt(value -> value.get()).sum() < sliceMax;}//请求来临....public void req(){Long key = getKey();//如果时间窗口未到达当前时间片,稍微等待一下//其实是一个保护措施,放置心跳对滑动窗口的推动滞后于当前请求while (linkedList.getLast()<key){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}//开始检查,如果未达到上限,返回ok,计数器增加1//如果任意一项达到上限,拒绝请求,达到限流的目的//这里是直接拒绝。现实中可能会设置缓冲池,将请求放入缓冲队列暂存if (checkCurrentSlice() && checkAllCount()){map.get(key).incrementAndGet();System.out.println(key+"=通过:"+map);}else {System.out.println(key+"=拒绝:"+map);}}public static void main(String[] args) throws InterruptedException {Window window = new Window();//模拟10个离散的请求,相对之间有200ms间隔。会造成总数达到上限而被限流for (int i = 0; i < 10; i++) {Thread.sleep(200);window.req();}//等待一下窗口滑动,让各个片的计数器都置零Thread.sleep(3000);//模拟突发请求,单个片的计数器达到上限而被限流System.out.println("---------------------------");for (int i = 0; i < 10; i++) {window.req();}}
}
结果
1701766769=通过:{1701766767=0, 1701766768=0, 1701766769=1}
1701766769=通过:{1701766767=0, 1701766768=0, 1701766769=2}
1701766769=通过:{1701766767=0, 1701766768=0, 1701766769=3}
1701766769=通过:{1701766767=0, 1701766768=0, 1701766769=4}
step:1701766770:{1701766768=0, 1701766769=4, 1701766770=0}
1701766770=通过:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒绝:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒绝:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒绝:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒绝:{1701766768=0, 1701766769=4, 1701766770=1}
step:1701766771:{1701766769=4, 1701766770=1, 1701766771=0}
1701766771=拒绝:{1701766769=4, 1701766770=1, 1701766771=0}
step:1701766772:{1701766770=1, 1701766771=0, 1701766772=0}
step:1701766773:{1701766771=0, 1701766772=0, 1701766773=0}
step:1701766774:{1701766772=0, 1701766773=0, 1701766774=0}
---------------------------
1701766774=通过:{1701766772=0, 1701766773=0, 1701766774=1}
1701766774=通过:{1701766772=0, 1701766773=0, 1701766774=2}
1701766774=通过:{1701766772=0, 1701766773=0, 1701766774=3}
1701766774=通过:{1701766772=0, 1701766773=0, 1701766774=4}
1701766774=通过:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒绝:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒绝:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒绝:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒绝:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒绝:{1701766772=0, 1701766773=0, 1701766774=5}
step:1701766775:{1701766773=0, 1701766774=5, 1701766775=0}
step:1701766776:{1701766774=5, 1701766775=0, 1701766776=0}
step:1701766777:{1701766775=0, 1701766776=0, 1701766777=0}
step:1701766778:{1701766776=0, 1701766777=0, 1701766778=0}
典型场景
滑动窗口算法,在tcp协议发包过程中被使用。在web现实场景中,可以将流量控制做更细化处理,解决计数器模型控制力度太粗暴的问题。
相关文章:

数据结构和算法专题---4、限流算法与应用
本章我们会对限流算法做个简单介绍,包括常用的限流算法(计数器、漏桶算法、令牌桶案发、滑动窗口)的概述、实现方式、典型场景做个说明。 什么是限流算法 限流是对系统的一种保护措施。即限制流量请求的频率(每秒处理多少个请求…...

亚信安慧AntDB受邀分享核心业务系统全域数据库替换实践
近日,亚信安慧AntDB数据库凭借丰富的核心业务系统升级替换能力和经验,受邀参与IT168组织的第三期“国产软硬件升级替换之路”的直播沙龙。 亚信安慧AntDB数据库相关负责人发表《基于AntDB的CRM全域数据库替换实践》的精彩演讲,通过通信行业率…...

1.uniapp基础
1.uniapp基础 官方文档:uni-app官网 1.1开发工具 (1)工具: HBuilderX HBuilderX-高效极客技巧 1.2 新建项目 (1) 文件》新建项目 (2)选择相应的配置信息,填写项目根路…...

typescript中的策略模式
typescript中的策略模式 当我们需要以整洁、易于维护和易于调试的方式构建应用程序时,使用设计模式是一种非常好的方式。 在本文中,我们的目标是阐明如何将策略模式无缝地集成到我们的应用程序中。如果我们熟悉依赖性注入,可能会发现策略模…...

Hadoop学习笔记(HDP)-Part.16 安装HBase
目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …...

C语言练习记录(蓝桥杯练习)(小蓝数点)
目录 小蓝数点 第一题程序的输出结果是?: 第二题下面代码的执行结果是什么?: 第三题下面代码的执行结果是什么?: 第四题关于关系操作符说法错误的是?: 第五题对于下面代码段,y的值为? 第六题sum 21 …...

RPG项目01_层级设置
基于“RPG项目01_UI面板Game”, 找到狼人 添加组件,让狼人一定区域自动跟随主角进行攻击 解释:【烘培蓝色】因为如果什么都不做就会被烘培成蓝色对应的功能就是 可修改区域功能 当将区域设置成不可行走状态,则不为蓝色 烘培&…...

相关基础知识
本文引注: https://zhuanlan.zhihu.com/p/447221519 1.方差 2.自协方差矩阵 3.自相关矩阵 4.互协方差矩阵 5.互相关矩阵 6.相关系数 7.自相关函数、自协方差函数与功率谱密度 8.互相关函数、互协方差函数与互功率谱密度...
基于单片机的智能健康监测手环的设计
目 录 1 绪论... 2 1.1 引言... 2 1.2 智能手环的国内外研究现状... 2 1.3 课题的研究意义... 3 1.4 本文的研究内容和章节安排... 4 2 智能手环系统设计方案... 5 2.1 系统总体设计方案... 5 2.2 主芯片选择... 5 2.3 显示方案的选择... 6 2.4 倾角传感器的选择... 6 2.5 心率…...
boost-字符串处理-判断-查找-裁剪-删除-替换-分割-合并
文章目录 1.判断1.1.equals1.2.all1.3.starts_with1.4.ends_with1.5.contains 2.大小写转换3.字符串删除4.字符串替换5.字符串查找6.字符串修剪7.字符串分割8.字符串合并9.总结 1.判断 判别式函数和分类函数大多数都是以is_开头,这些函数如下: 判别式函…...

Django 开发 web 后端,好用过 SpringBoot ?
基础语法 Django(Python):以简洁和直观著称。它允许更快的开发速度,特别适合快速迭代的项目。例如,一个简单的视图函数: from django.http import HttpResponsedef hello_world(request):return HttpRespon…...
【矩阵】54.螺旋矩阵(顺时针打印矩形元素)
题目 class Solution {public List<Integer> spiralOrder(int[][] matrix) {int m matrix.length, n matrix[0].length;int leftUpM 0, leftUpN 0, rightDownM m - 1, rightDownN n - 1;List<Integer> res new ArrayList<>();while (leftUpM < ri…...

【数据中台】开源项目(5)-Amoro
介绍 Amoro is a Lakehouse management system built on open data lake formats. Working with compute engines including Flink, Spark, and Trino, Amoro brings pluggable and self-managed features for Lakehouse to provide out-of-the-box data warehouse experience,…...
_WorldSpaceLightPos0的含义 UNITY SHADER
_WorldSpaceLightPos0 为当前平行光的方向,方向是从光源到照射的方向。 因此,如果要算法线和平行光之间的夹角, 则需要首先将归一化的_WorldSpaceLightPos0去负数。这样才能继续去计算。 也就是: fixed3 reflectdirnormalize…...

iOS不越狱自动挂机
自动挂机在电脑上或者安卓手机上都相对容易,而在不越狱的iOS设备上还是有点难度的。 此方法不是我原创,详情见: 【苹果党福音,ios也能用的挂机脚本】 https://www.bilibili.com/video/BV1sv4y1P7TL/?share_sourcecopy_web&v…...

智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鼠群算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…...

FL Studio中如何录音的技巧,让你的声音更加出众哦!
Hey小伙伴们!今天我要和大家分享一下在FL Studio中如何录音的技巧,让你的声音更加出众哦! 编曲软件FL Studio 即“Fruity Loops Studio ”,也就是众所熟知的水果软件, 全能音乐制作环境或数字音频工作站࿰…...
前端React基础面试题
1,说说react里面bind函数与箭头函数 bind 由于在类中,采用的是严格模式,所以事件回调的时候会丢失this指向,指向的undefined,需要使用bind来给函数绑定上当前实例的this指向。 箭头函数的this指向上下文,所以永久能拿到当前组件实例的。this指向我们可以完美的使用箭头…...
【1day】致远A6系统任意文件下载漏洞学习
注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞描述 二、影响版本 三、资产测绘 四、漏洞复现...
朝花夕拾华山平台流水账
2022年8月25日,我加入了诚迈科技(南京),加入了华山平台。 跟我一起入职平台的还有三个小伙伴:小帅、小阳、小甘。 小帅能力很强,前后端都会,入职各种考试工具人。 小阳毕业没多久,一…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案
一、延迟敏感行业面临的DDoS攻击新挑战 2025年,金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征: AI驱动的自适应攻击:攻击流量模拟真实用户行为,差异率低至0.5%,传统规则引…...
Python的__call__ 方法
在 Python 中,__call__ 是一个特殊的魔术方法(magic method),它允许一个类的实例像函数一样被调用。当你在一个对象后面加上 () 并执行时(例如 obj()),Python 会自动调用该对象的 __call__ 方法…...

鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...