高并发场景下常见的限流算法及方案介绍
应用场景
现代互联网很多业务场景,比如秒杀、下单、查询商品详情,最大特点就是高并发,而往往我们的系统不能承受这么大的流量,继而产生了很多的应对措施:CDN、消息队列、多级缓存、异地多活。
但是无论如何优化,终究由硬件的物理特性决定了我们系统性能的上限,如果强行接收所有请求,往往造成雪崩。
这时候限流熔断就发挥作用了,限制请求数,快速失败,保证系统满负载又不超限。
极致的优化,就是将硬件使用率提高到 100%,但永远不会超过 100%
常用限流算法
1. 计数器
直接计数,简单暴力,举个例子:
比如限流设定为 1 小时内 10 次,那么每次收到请求就计数加一,并判断这一小时内计数是否大于上限 10,没超过上限就返回成功,否则返回失败。
这个算法的缺点就是在时间临界点会有较大瞬间流量。
继续上面的例子,理想状态下,请求匀速进入,系统匀速处理请求:
但实际情况中,请求往往不是匀速进入,假设第 n 小时 59 分 59 秒的时候突然进入 10 个请求,全部请求成功,到达下一个时间区间时刷新计数。那么第 n+1 小时刚开始又打进 10 个请求,等于瞬间进入 20 个请求,肯定不符合 “1 小时 10 次” 的规则,这种现象叫做 “突刺现象”。
为解决这个问题,计数器算法经过优化后,产生了滑动窗口算法:
我们将时间间隔均匀分隔,比如将一分钟分为 6 个 10 秒,每一个 10 秒内单独计数,总的数量限制为这 6 个 10 秒的总和,我们把这 6 个 10 秒成为 “窗口”。
那么每过 10 秒,窗口往前滑动一步,数量限制变为新的 6 个 10 秒的总和,如图所示:
那么如果在临界时,收到 10 个请求(图中灰色格子),在下一个时间段来临时,橙色部分又进入 10 个请求,但窗口内包含灰色部分,所以已经到达请求上线,不再接收新的请求。
这就是滑动窗口算法。
但是滑动窗口仍然有缺陷,为了保证匀速,我们要划分尽可能多的格子,而格子越多,每一个格子能够接收的请求数就越少,这样就限制了系统瞬间处理能力。
2. 漏桶
漏桶算法其实也很简单,假设我们有一个固定容量的桶,流速(系统处理能力)固定,如果一段时间水龙头水流太大,水就溢出了(请求被抛弃了)。
用编程的语言来说,每次请求进来都放入一个先进先出的队列中,队列满了,则直接返回失败。另外有一个线程池固定间隔不断地从这个队列中拉取请求。
消息队列、jdk 的线程池,都有类似的设计。
3. 令牌桶
令牌桶算法比漏桶算法稍显复杂。
首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token 以一个固定的速率往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
漏桶和令牌桶算法的区别:
漏桶的特点是消费能力固定,当请求量超出消费能力时,提供一定的冗余能力,把请求缓存下来匀速消费。优点是对下游保护更好。
令牌桶遇到激增流量会更从容,只要存在令牌,则可以一并消费掉。适合有突发特征的流量,如秒杀场景。
限流方案
一、容器限流
1. Tomcat
tomcat 能够配置连接器的最大线程数属性,该属性 maxThreads
是 Tomcat 的最大线程数,当请求的并发大于 maxThreads
时,请求就会排队执行 (排队数设置:accept-count),这样就完成了限流的目的。
<Connectorport="8080"protocol="HTTP/1.1"connectionTimeout="20000"maxThreads="150"redirectPort="8443"/>
2. Nginx
Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数。
-
控制速率
我们需要使用
limit_req_zone
配置来限制单位时间内的请求数,即速率限制,示例配置如下:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
第一个参数:$binary_remote_addr 表示通过 remote_addr 这个标识来做限制,“binary_” 的目的是缩写内存占用量,是限制同一客户端 ip 地址。
第二个参数:zone=mylimit:10m 表示生成一个大小为 10M,名字为 one 的内存区域,用来存储访问的频次信息。
第三个参数:rate=2r/s 表示允许相同标识的客户端的访问频次,这里限制的是每秒 2 次,还可以有比如 30r/m 的。
-
并发连接数
利用
limit_conn_zone
和
limit_conn
两个指令即可控制并发数,示例配置如下
limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; server { ...limit_conn perip 10; # 限制同一个客户端iplimit_conn perserver 100; }
只有当 request header 被后端处理后,这个连接才进行计数
二、服务端限流
1. Semaphore
JUC 包中提供的信号量工具,它的内部维护了一个同步队列,我们可以在每个请求进来的时候,尝试获取信号量,获取不到可以阻塞或者快速失败
简单样例:
Semaphore sp = new Semaphore(3);
sp.require(); // 阻塞获取
System.out.println("执行业务逻辑");
sp.release();
2. RateLimiter
Guava 中基于令牌桶实现的一个限流工具,使用非常简单,通过方法 create()
创建一个桶,然后通过 acquire()
或者 tryAcquire()
获取令牌:
RateLimiter rateLimiter = RateLimiter.create(5); // 初始化令牌桶,每秒往桶里存放5个令牌
rateLimiter.acquire(); // 自旋阻塞获取令牌,返回阻塞的时间,单位为秒
rateLimiter.tryAcquire(); // 获取令牌,返回布尔结果,超过超时时间(默认为0,单位为毫秒)则返回失败
RateLimiter 在实现时,允许暴增请求的突发情况存在。
举个例子,我们有一个速率为每秒 5 个令牌的 RateLimiter:
当令牌桶空了的时候,如果继续获取一个令牌,那么会在下一次补充令牌的时候返回结果
但如果直接获取 5 个令牌,并不是等待桶内补齐 5 个令牌后再返回,而是仍旧会在令牌桶补充下一个令牌的时候直接返回,而预支令牌所需的补充时间会在下一次请求时进行补偿
public void testSmoothBursty() {RateLimiter r = RateLimiter.create(5);for (int i = 0; i++ < 2; ) { System.out.println("get 5 tokens: "+ r.acquire(5)+"s");System.out.println("get 1 tokens: "+ r.acquire(1)+"s");System.out.println("get 1 tokens: "+ r.acquire(1)+"s");System.out.println("get 1 tokens: "+ r.acquire(1)+"s");System.out.println("end");}
}/**
* 控制台输出
* get 5 tokens: 0.0s 初始化时桶是空的,直接从空桶获取5个令牌
* get 1 tokens: 0.998068s 滞后效应,需要替前一个请求进行等待
* get 1 tokens: 0.196288s
* get 1 tokens: 0.200391s
* end
* get 5 tokens: 0.195756s
* get 1 tokens: 0.995625s 滞后效应,需要替前一个请求进行等待
* get 1 tokens: 0.194603s
* get 1 tokens: 0.196866s
* end
*/
3. Hystrix
Netflix 开源的熔断组件,支持两种资源隔离策略:THREAD(默认)或者 SEMAPHORE
- 线程池:每个 command 运行在一个线程中,限流是通过线程池的大小来控制的
- 信号量:command 是运行在调用线程中,但是通过信号量的容量来进行限流
线程池策略对每一个资源创建一个线程池以进行流量管控,优点是资源隔离彻底,缺点是容易造成资源碎片化。
使用样例:
// HelloWorldHystrixCommand要使用Hystrix功能
public classHelloWorldHystrixCommandextendsHystrixCommand{ private final String name; publicHelloWorldHystrixCommand(String name){ super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); this.name = name; } // 如果继承的是HystrixObservableCommand,要重写Observable construct() @Override protectedStringrun(){ return "Hello " + name; }
}
调用该 command:
String result = new HelloWorldHystrixCommand("HLX").execute();
System.out.println(result); // 打印出Hello HLX
Hystrix 已经在 2018 年停止开发,官方推荐替代项目 Resilience4j
更多使用介绍可查看:Hystrix 熔断器的使用
4. Sentinel
阿里开源的限流熔断组件,底层统计采用滑动窗口算法,限流方面有两种使用方式:API 调用和注解,内部采插槽链来统计和执行校验规则。
通过为方法增加注解 @SentinelResource(String name)
或者手动调用 SphU.entry(String name)
方法开启流控。
使用 API 手动调用流控示例:
@Test
public void testRule() {// 配置规则.initFlowRules();int count = 0;while (true) {try (Entry entry = SphU.entry("HelloWorld")) {// 被保护的逻辑System.out.println("run " + ++count + " times");} catch (BlockException ex) {// 处理被流控的逻辑System.out.println("blocked after " + count);break;}}
}// 输出结果:
// run 1 times
// run 2 times
// run 3 times
关于 Sentinel 的详细介绍可查看:Sentinel - 分布式系统的流量哨兵
三、分布式下限流方案
线上环境下,如果对共用资源(如数据库、下游服务)做统一流量限制,那么单机限流显然不能满足,而需要分布式流控方案。
分布式限流主要采取中心系统流量管控的方案,由一个中心系统统一管控流量配额。
这种方案的缺点就是中心系统的可靠性,所以一般需要备用方案,在中心系统不可用时,退化为单机流控。
\1. Tair 通过 incr 方法实现简单窗口
实现方式是使用 incr()
自增方法来计数并与阈值进行大小比较。
public boolean tryAcquire(String key) {// 以秒为单位构建tair的keyString wrappedKey = wrapKey(key);// 每次请求+1,初始值为0,key的有效期设置5sResult<Integer> result = tairManager.incr(NAMESPACE, wrappedKey, 1, 0, 5);return result.isSuccess() && result.getValue() <= threshold;
}private String wrapKey(String key) {long sec = System.currentTimeMillis() / 1000L;return key + ":" + sec;
}
【备注】incr 方法的参数说明
// 方法定义:
Result incr(int namespace, Serializable key,int value,int defaultValue,int expireTime)/* 参数含义:
namespace - 申请时分配的 namespace
key - key 列表,不超过 1k
value - 增加量
defaultValue - 第一次调用 incr 时的 key 的 count 初始值,第一次返回的值为 defaultValue + value。
expireTime - 数据过期时间,单位为秒,可设相对时间或绝对时间(Unix 时间戳)。
*/
2. Redis 通过 lua 脚本实现简单窗口
与 Tair 实现方式类似,不过 redis 的 incr()
方法不能原子性的设置过期时间,所以需要使用 lua 脚本,在第一次调用返回 1 时,设置下过期时间为 1 秒。
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then redis.call("expire",KEYS[1],1)
end
return current
3. Redis 通过 lua 脚本实现令牌桶
实现思路是获取令牌后,用 SET 记录 “请求时间” 和 “剩余 token 数量”。
每次请求令牌时,通过这两个参数和请求的时间、流速等参数进行计算,返回是否获取令牌成功。
获取令牌 lua 脚本:
local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token')
local last_time = ratelimit_info[1]
local current_token = tonumber(ratelimit_info[2])
local max_token = tonumber(ARGV[1])
local token_rate = tonumber(ARGV[2])
local current_time = tonumber(ARGV[3])
local reverse_time = 1000/token_rateif current_token == nil thencurrent_token = max_tokenlast_time = current_time
elselocal past_time = current_time-last_timelocal reverse_token = math.floor(past_time/reverse_time)current_token = current_token+reverse_tokenlast_time = reverse_time*reverse_token+last_timeif current_token>max_token thencurrent_token = max_tokenend
endlocal result = 0
if(current_token>0) thenresult = 1current_token = current_token-1
end redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token)
redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_token-current_token)+(current_time-last_time)))
return result
初始化令牌桶 lua 脚本:
local result=1
redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[1],"curr_permits",ARGV[2],"max_burst",ARGV[3],"rate",ARGV[4])
return result
相关文章:

高并发场景下常见的限流算法及方案介绍
应用场景 现代互联网很多业务场景,比如秒杀、下单、查询商品详情,最大特点就是高并发,而往往我们的系统不能承受这么大的流量,继而产生了很多的应对措施:CDN、消息队列、多级缓存、异地多活。 但是无论如何优化&…...

虹科分享 | 选择SAS还是NVMe?虹科网络基础带您一探究竟!
存储架构师需要通过确保他们选择的存储解决方案提供支持其生态系统所需的安全性、稳定性、可扩展性和管理特性来应对当今的业务挑战。当他们考虑采用新的存储技术时,在采用新技术之前,他们应该权衡和审查一些基本的考虑因素。新的存储协议不断进入市场&a…...

在ERP管理系统中,库存管理的基本流程是什么?
在ERP管理系统中,库存管理的基本流程是什么? 下面我就以我们公司正在用的简道云库存管理系统为例,为大家进行库存管理基本流程的演示 这个系统是我们公司自己搭建的,大家如果有需要可以自取,也可以在模板的基础上自行…...
Ruby 之 csv 文件读写
csv 文件写入 require csvtitle ["col1", "col2"] contents [["row11", "row12"], ["row21", "row22"]]csv1 CSV.open("test1.csv", "wb") do |csv|# write file titlecsv << titl…...
Android AMS——进程LRU列表更新(十七)
AMS对进程的管理主要体现在两个方面: 进程LRU列表动态更新:动态调整进程在mLruProcesses列表的位置进程优先级动态调整:实际是调整进程oom_adj的值。 这两项调整和系统进行自动回收有关,当内存不足时,系统会关闭一些进程来释放内存,下面就依据这两方面来看…...

【数据可视化】—大屏数据可视化展示
【数据可视化】—大屏数据可视化展示 一、数据可视化 数据可视化的目的:借助于图形化工具,清晰有效的传达与沟通信息。 数据可视化可以把数据从冰冷的数字转换成图形,揭示蕴含在数据中的规律和道理。 二、 免费数据可视化库 Echarts 百度…...

计算机算法分析与设计(12)---贪心算法(最优装载问题和哈夫曼编码问题)
文章目录 一、最优装载问题1.1 问题表述1.2 代码编写 二、哈夫曼编码2.1 哈夫曼编码概述2.2 前缀码2.3 问题描述2.4 代码思路2.5 代码编写 一、最优装载问题 1.1 问题表述 1. 有一批集装箱要装上一艘载重量为 c c c 的轮船,已知集装箱 i ( 1 ≤ i ≤ n ) i(1≤i≤…...

打造属于自己的vue图标库
hfex-icon图标库 Install npm i -D hfex-icon主要提供2种使用方式 方式一 通过svg图标资源,借助unplugin-icons库将svg图标文件生成vue组件,然后通过vue组件的引入方式在vue中使用 unplugin-icons 兼容vue2和vue3 在vue.config.js的plugins中配置…...
C++11线程池
使用 condition_variable::wait(unique_lock<mutex>&lck, Predicate pred) 时,必须保证条件变量通过notify唤醒的同时,wait 的第二个参数 Predicate 返回 true 了才可以往下走。必须两个条件同时满足,如果notify的时候Predicate返回…...

企业打造VR虚拟展厅,开启商务洽谈新时代!
现代化数字营销中,企业做了虚拟线上展厅和不做虚拟展厅的对比是很明显的,VR虚拟展厅让企业产品、企业环境、企业实力的展示更加真实、直观。虚拟展厅是一种在线展示企业形象和品牌的新型方式,随着VR技术的发展,虚拟展厅正在逐步取…...

linux部署gitlab
1. 配置yum源: vim /etc/yum.repos.d/gitlab-ce.repo [gitlab-ce] nameGitlab CE Repository baseurlhttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el$releasever/ gpgcheck0 enabled1 2. 更新本地缓存 sudo yum install -y gitlab-ce 3. 安装相关依赖 yum …...

c++_learning-基础部分
文章目录 基础认识:语言特性(面向对象编程):c的类(相当于c中的结构体):三大特性:c包含四种编程范式:优缺点: c程序编译的过程:预处理->编译&am…...

支持PC端、手机端、数据大屏端的Spring Cloud智慧工地云平台源码
技术架构:微服务JavaSpring Cloud VueUniApp MySql 智慧建筑工地云平台主要利用大数据、物联网等技术,整合工地信息、材料信息、工程进度等,实现对建筑项目的全程管理。它可以实现实时监测和控制,有效解决施工中的问题,…...

给cmd控制台程序 套壳 美化
给cmd控制台程序套壳美化,可以获取程序的标准输出和报错信息。 # _*_ coding: utf-8 _*_ """ 控制台程序启动器,杜绝黑窗口。 Time: 2023/10/18 15:28 Author: Jyun Version: V 0.1 File: main.py Blog: https://ctrlcv.…...
【系统架构设计】架构核心知识: 1 构件和中间件
目录 一 构件 1 构件的特性 2 构件、对象和模块的对比 3 构件的复用...

通过开发者工具-网络排查响应时间过长的问题
关键词:network 网络 pending 开发者工具 有时候我们会发现某次http请求花费了很长时间,比如会花费十几秒,那么我们可以通过开发者工具的网络和其他一些工具来分析请求时间过长的原因 Dev Tool 中时间线各阶段代表的意义 分别用edge、chorm…...
【Python】Python 实现 Excel 到 CSV 的转换程序
Python 实现 Excel 到 CSV 的转换程序 Excel 可以将电子表格保存为 CSV 文件,只要点几下 鼠标,但如果有几百个 Excel文件要转换为 CSV , 就需要点击几小时。利用 openpyxl 模块, 编程读取当前工作目录中的所有 Excel 文件&#x…...

BUUCTF题解之[极客大挑战 2019]Havefun 1
1.题目分析 使用浏览器开发者工具查看网页源码,查看疑似flag的代码。 (特别是注释了的源码,一般是HTML,JS,PHP的源码) 修改统一资源定位符URL访问服务器后端接口,拿到flag。 1.URL URL是统一资源定位符(…...

DIV+CSS网页布局
本文参考 https://blog.csdn.net/ZhangJiWei_2019/article/details/114669722 二、浮动 浮动的元素会向左或向右浮动,直到碰到前面已经有浮动的元素或者是其父元素的边框为止。浮动的元素会脱离文档流(不再占有原来的位置)。 (…...

python二次开发CATIA:CATIA Automation
CATIA 软件中有一套逻辑与关系都十分严谨的自动化对象,它们从CATIA(Application)向下分支。每个自动化对象(Automation Object,以下简称Object)都有各自的属性与方法。我们通过程序语言调用这些 Object 的属性与方法,便…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...