当前位置: 首页 > news >正文

「并发编程实战」常见的限流方案

「并发编程实战」常见的限流方案

文章目录

  • 「并发编程实战」常见的限流方案
    • 一、概述
    • 二、计数器限流方案
    • 三、时间窗口限流方案
    • 四、令牌桶限流方案
    • 五、漏桶限流方案
    • 六、高并发限流算法小结

文章参考:

追忆四年前:一段关于我被外企CTO用登录注册吊打的不堪往事

新来个技术总监,把限流实现的那叫一个优雅,佩服!

接口限流算法总结

一、概述

曾经在一个大神的里看到这样一句话:在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

什么是限流呢?限流是限制到达系统的并发请求数量,保证系统能够正常响应部分用户请求,而对于超过限制的流量,则通过拒绝服务的方式保证整体系统的可用性

根据限流作用范围,可以分为单机限流和分布式限流;根据限流方式,又分为计数器、滑动窗口、漏桶限令牌桶限流,下面我们对这块详细进行讲解。


二、计数器限流方案

计数器方案属于限流算法中最简单、并且实现难度最低的算法,比如以短信接口调用为例规定了「短信」接口的调用频率,不允许在十分钟内超出三次

这时实现起来就很简单,在「短信」接口的类中,创建一个Map<String,AtomicInteger>类型的容器即可,其中Key存储用户ID,而Value则存储一个原子计数器,每当一个用户调用一次短信接口后,就将容器中对应的计数器加一,同时开启一个定时任务,每十分钟对计数器做归零重置。

当然,上述这种做法在用户量较大的情况下,显然会对程序造成较大的性能损耗,假设有100W用户,那就需要维护100W个计数器,这会使得内存占用率直线飙升,同时还需要创建100W个定时器,来分别维护每个用户的调用计数器。

更好一些的做法是借助中间件实现,比如基于Redis缓存中间件来完成,将用户ID设计成Key,而Value则是计数器,并且创建每个Key时将过期时间指定为10s,这样就能充分利用资源,不会造成太大的资源与性能开销,伪逻辑如下:

@Autowired
private StringRedisTemplate redis;@RequestMapping("/sendSmsVerification")
public ResultVO sendSmsVerification(String sign, String userId){// 用 SMS_ 拼接用户ID作为KeyString userIdSMS = "SMS_" + userId;// 先通过前面生成的Key去Redis中进行查询String value = redis.opsForValue().get(userIdSMS);// 如果目前已经达到了调用次数限制if ("3".equals(value)) {return new ResultVO(200, "短信调用次数已达上限,请在十分钟后重试...");}// 如果该用户的Key在Redis中不存在,说明是第一次调用短信接口if ("".equals(value)) {// 首次调用短信接口时,则在Redis中创建一个计数器redis.opsForValue().set(lockKey, 1, 10, TimeUnit.SECONDS);} // 如果该用户的Key在Redis中存在,说明并非第一次调用短信接口else {// 此时则通过Redis的incr命令,把对应的计数器加一redis.opsForValue().increment(key);}// 省略其他业务代码......
}
复制代码

这段限流代码并不算特别复杂,整体下来无非还是前面说的那几步:

  • ①先通过用户ID拼接得到Key,然后去Redis中进行查询。
  • ②如果查询出的结果为3,说明目前已达到了调用限制,则直接返回调用已达上限。
  • ③如果查询出的结果为空,则说明用户是第一次调用短信接口,此时则在Redis中创建计数器。
  • ④如果查询出的value和上面两条都不匹配,则对Redis中的计数器加一。

这种计数器限流算法实现起来尤为简单,但前面也聊过它所存在的问题:临界问题,如果在两个时间单位的临界处调用,比如在第9:59秒调用了三次,接着又在第10:01秒调用了三次,那依旧会发生“超出调用上限”的情况,毕竟以十分钟作为单位,第9、10分钟属于一个时间单位内,这时就超出了调用上限,调用次数达到6次。


三、时间窗口限流方案

时间窗口限流方案被提出的主要目的,就是为了解决传统的计数器方案存在的临界问题,它的演变前身为TCP协议的滑动窗口,如果对于TCP协议较为熟悉的小伙伴,听到这个词汇相信一定不陌生,如若对这块内容并不熟悉的小伙伴也没关系,可参考之前文章中聊过的《TCP粘包、半包问题-滑动窗口》。

限流方案中的时间窗算法,主要可被分为固定窗口限流、滑动窗口限流两种方案,而前面聊到的计数器方案,实际上就是一种特殊的固定窗口限流方案,在前面的例子中,时间窗口大小为10min,速率限制为3次,这种方案存在明显的临界限制问题。

下面重点聊一聊滑动时间窗口,这种方案是解决临界问题而被提出的,但对于滑动窗口的概念有些不好理解,所以先上一副逻辑图,如下:

image-20230302135348167

在上图中,整个用虚红线圈出来的代表一个时间窗口,以上述例子来说,一个窗口的大小为600s/10min,并且每个窗口被分为了三个单位,每个单位大小是200s,这也就意味着每过200s,窗口会向后滑动一个单位,这个动作也可以被称之为向后滑动一格,目前的窗口分布如下:

  • 第一格:0~200s
  • 第二格:201~400s
  • 第三格:401~600s

划分出来的每个格子,都具备各自独立的计数器,比如在第138s时发生了一次接口调用,此时第一格的计数器就会+1,还是以之前的例子来说:

9:59秒调用了三次,接着又在第10:01秒调用了三次。

将这里的分钟转换为具体秒数,也就是在第599s调用了三次,第601s调用了三次,此时来看,每当时间过去200s,窗口就会向后滑动一格,这也就意味着整个窗口会变成图中的下面的样子,此时的窗口分布为:

  • 第一格:201~400s
  • 第二格:401~600s
  • 第三格:601~800s

当第599s调用了三次「短信」接口后,第二格的计数器会累加到3,此时再当第601s尝试调用「短信」接口时,就会检测出已达到调用上限,此时就会拒绝用户的调用,以此来解决传统计数器方案的临界问题。

Why?Why?Why?有些小伙伴可能到这里就有些晕了,第601s是如何检测出调用超额的呐?因为目前的时间窗口范围是201~800s,而将整个时间窗口内的计数器求和,就会得到调用总次数为3,因而成功检测出了第601s的调用上限。

当出现调用达到上限时,必须随着时间推移、窗口不断向后滑动,这样整个窗口的计数器总和才会下降,因此用户才能继续调用,通过这种方式就能控制一个时间段的绝对限流。

但滑动窗口限流方案就不存在临界问题吗?答案是No,依旧存在,Why?来看下图:

image-20230302135519519

看上图中给出的案例,因为目前的时间窗口大小是600s,而199s~203s显然处于同一个时间窗口范围内,但随着窗口向后滑动,这里依旧会出现临界问题,也就是在一个窗口范围内,同样会出现打破调用次数上限的情况,那这种情况下又该如何解决呢?其实答案很简单,把一个窗口的格子单位调小即可。

比如直接将每一格的单位大小从200s调整为1s,此时每过一秒钟,窗口就会向后滑动一格,等到100s秒过后,窗口会向后滑动100格,此时窗口的区间范围是101~700s,这就将199~203s这个范围包含了进去,因此上述情况自然就不会出现!

经过上述分析由此可以得出一条准则:当滑动窗口的格子划分的单位越小,整个窗口中的格子数量会越多,滑动窗口的向后移动就越平滑,限流的统计就会越精确


四、令牌桶限流方案

前面简单聊完了时间窗口限流方案后,接着再来聊一聊大名鼎鼎的令牌桶限流方案,令牌桶算法是一种类似于“池化”思想的产物,算法的大体过程如下:

image-20230302135819074

  • ①初始化令牌桶并设置最大令牌数,当桶内的令牌达到阈值时,新添加的令牌会被拒绝或丢弃。
  • ②根据限流大小,启动一条线程,并按照一定速率向令牌桶中不断添加新的令牌。
  • ③任何处于「限流范围」内的请求,都需要先获取到一个可用令牌,然后才会被处理。
  • ④当一个请求获取到可用令牌后,才会真正执行业务逻辑,执行完成后会将此令牌从桶内移除。
  • ⑤令牌桶除开有最大令牌数外,也会有最小令牌数,当桶内令牌数小于最小阈值时,处理完请求并不会移除令牌,而是会将令牌还给令牌桶。

对于令牌桶限流算法,理解起来并没有前面的滑动时间窗口复杂,但唯一要注意的是:当桶内的令牌被一个请求获取后,此时并不会立马从桶内移除,该令牌会依旧停留在桶内,只不过该令牌的状态会从可用状态变为不可用状态,也就是其他请求无法再获取该令牌,真正移除令牌的工作,会在业务逻辑执行完成之后才触发。


五、漏桶限流方案

漏桶限流和令牌桶限流都属于桶类型的算法,但漏桶算法更类似于MQ消息队列,其算法的执行示意图如下:

image-20230302135916397

想要理解漏桶算法,咱们先来看看日常生活中的漏斗,比如现在我要用漏斗来给摩托车加油:

image-20230302135930357

倒油时,我们可以用瓶子,也可以用桶子,也可以用加油枪…,这也就意味着:漏斗上方的进油速率并不固定,但不管上方的进油速率如何,下方的漏斗出口,其速率确实固定的,无论上方进油多快,都不能影响下方的出油速率。

理解了日常生活中的漏斗后,接着再来看看前面的漏桶限流算法,请求会从漏桶上方进入,而服务端则只会按照固定速率去处理请求。此时思考一个问题:当请求进入的速率大于请求处理的速率,会发生什么情况呢

此时依旧回到用漏斗给摩托车加油的例子中,如果漏斗上方的倒油速度比较快,而由于漏斗的结构原因,下方的出口跟不上进油速度,此时漏斗中的油量会直线上升,直到超出漏斗的最大容量时,再进入漏斗的汽油会溢出。

而限流中的漏桶算法同样如此,请求进入的速率大于请求处理的速率时,多出来的请求会被放入桶中等待,当桶内阻塞等待的请求超过最大限制后,后续进入的请求会被丢弃或拒绝。

从上述的讲解中,诸位应该能够明显感受到漏桶算法的特点,即:宽进严出,该算法中不会限制请求进入的速率,但会限制请求处理的速率,一些对稳定性要求较高的系统,就可以采用该算法对系统进行限流。当然,如果熟悉MQ的小伙伴也能感受出:漏桶算法和MQ的削峰填谷有着异曲同工之妙,当系统峰值流量较高时,会将请求写入到MQ中,然后再由具体的业务服务,按照固定的速率拉取MQ中的消息进行处理


六、高并发限流算法小结

计数器 VS 滑动窗口

计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大

漏桶算法 VS 令牌桶算法

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。

在前面共计提到了计数器、滑动窗口、令牌桶、漏桶这四种常规的限流方案,但要记住:并不存在一种适用于任何场景的限流算法,根据业务的需求不同,系统的关注面不同,应当采用不同的限流方案,没有所谓的最好!最后简单说一些成熟的限流实现:

  • Guava中的RateLimiter工具类:基于令牌桶实现的限流组件,并且对其进行了预热拓展。
  • Sentinel中的匀速排队限流策略:基于漏桶思想的限流策略,内部采用队列进行实现。
  • Nginxlimit_req_zone限流模块:基于漏桶思想的限流模块,实现网关层的限流控制。
  • ........

相关文章:

「并发编程实战」常见的限流方案

「并发编程实战」常见的限流方案 文章目录「并发编程实战」常见的限流方案一、概述二、计数器限流方案三、时间窗口限流方案四、令牌桶限流方案五、漏桶限流方案六、高并发限流算法小结文章参考&#xff1a; 追忆四年前&#xff1a;一段关于我被外企CTO用登录注册吊打的不堪往事…...

IO 复习

IO 把电脑硬盘中的数据读到程序中,称为输入,进行数据的read操作 把程序往外部设备写数据,称为输出,进行数据的write操作 File类 一个File对象可以表示计算机硬盘上的一个文件或目录(文件夹) 可以获取文件信息,创建文件,删除文件 但是不能对文件中的数据进行读写操作 一些…...

什么是项目管理

项目管理&#xff08;简称PM&#xff09;&#xff0c;就是将知识、技能、工具与技术应用于项目活动&#xff0c;以满足项目的要求。项目管理通过合理运用与整合特定项目所需的项目管理过程得以实现。项目管理使组织能够有效且高效地开展项目 “现代管理&#xff0c;项目就是一切…...

什么是入站营销?如何向合适的受众推销

没有什么比入站营销更有效地优先考虑客户体验了。 入站营销可为您的客户在他们需要的时间和地点准确提供他们想要的东西。它以最有机的方式在您的行业中建立信任、忠诚和权威。 什么是入站营销&#xff1f; 入站营销是一种商业方法&#xff0c;可提供优质内容和量身定制的客户…...

Qt 崩溃 corrupted double-linked list Aborted

文章目录摘要1 使用全局静态变量2 不取第一个和最后一个数3 将数据计算放到同一线程计算4 替换槽函数5 修改传值为const6 神奇的环境因素7 更神奇的板子差异8 另一个细节Aborted最后关键字&#xff1a; Qt、 Aborted、 corrupted、 double、 linked 摘要 额&#xff0c;结论&…...

牛逼了!这是什么神仙面试宝典?半月看完25大专题,居然斩获阿里P7offer

这是什么神仙面试宝典&#xff1f;半月看完25大专题&#xff0c;居然斩获阿里P7offer&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;容我小小的嘚瑟一下下啦~~这份神仙面试宝典总共有25大专题&#xff1a;专题一&#xff1a;JavaOOP面…...

单链表详解

单链表一.概念二.一些类型的创建三.尾插四.头插五.头删尾删六.打印链表七.单链表查找,任意位置插入&#xff0c;任意位置删除八.源代码一.概念 该篇链表博客是按照工程项目的格式来记录的&#xff0c;与平常的算法链表有些许不同&#xff0c;注意区分。 二.一些类型的创建 三.尾…...

【AUTOSAR-CanNM】-3.1-如何让ECU发出的首帧是NM帧(Tx Nm报文先于Tx App应用报文发出)

点击返回「《Autosar_BSW高阶配置》总目录」 案例背景(共5页精讲):该篇博文将告诉您: 如何让ECU发出的首帧/第一帧是网络管理NM报文/帧(Tx Nm报文先于Tx App应用报文发出) 目录 1 图解详述APP报文和NM报文是如何发送的...

html常用标签2和语法练习

目录 1.表单标签 form标签 input标签 选择框 复选框:checkbox 按钮框:button 文件选择框 多行编辑框:textarea 2.html语法练习 展示简历信息 填写简历信息 ​编辑 3.HTML特殊字符 1.表单标签 表单是让用户输入信息的重要途径 表单域:包含表单元素的区域,重点是form…...

【go语言之thrift协议三之client端分析】

go语言之thrift协议之client端分析runClientOpenprotocolFactory.GetProtocolhandleClientNewTStandardClientNewCalculatorClienthandleClient的具体实现上一篇文章分析了thrift协议server端的实现&#xff0c;这边还是基于官方的示例去分析。 import ("crypto/tls"…...

Codeforces Round #855 (Div. 3) A-E

传送门 A. Is It a Cat? 题意 给你一个只有英文字母的字符串&#xff0c;问你这个字符串是否由连续的’m’, ‘e’, ‘o’,‘w’,&#xff08;顺序不能改变&#xff09;构成&#xff0c;并且不区分大小写。 如&#xff1a; “meow”, “mmmEeOWww”, “MeOooOw” 是符合要求…...

3/3操作系统作业

目录 1.前趋图和程序执行 &#xff08;1&#xff09;前驱图 &#xff08;2&#xff09;程序的顺序执行 &#xff08;3&#xff09;程序的并发执行 2.进程的描述 &#xff08;1&#xff09;进程的定义与特征 ​编辑​编辑&#xff08;2&#xff09;进程控制块​编辑 &…...

「C/C++」 标准文件操作大全

一、设备文件&#xff08;运行程序时会默认打开这三个设备文件&#xff09; stdin&#xff1a;标准输入&#xff0c;默认为当前终端&#xff08;键盘&#xff09;&#xff0c;我们使用的scanf、getchar函数默认从此终端获得数据。stdout&#xff1a; 标准输出&#xff0c;默认…...

一款SAST工具需要支持多少种编译器呢?

除了Java语言&#xff0c;C#语言之外&#xff0c;C、C语言是编译器类型最多的编程语言&#xff0c;有几十种编译器&#xff0c;这些编译器方言为研发SAST工具带来了巨大的工作量&#xff0c;很多产品由于无法适配客户的编译器&#xff0c;导致无法检测。下面我们罗列一下国外和…...

jvm mat分析dump文件

jvm调优中&#xff0c;经常使用dump来分析是否存在大对象导致频繁full gc&#xff0c;以下为使用步骤&#xff1a; 一、获得服务进程 ps -ef | grep list-app | grep -v grep 二、生成dump文件 jmap -dump:formatb,filexxx.dump pid jmap -dump:filetest.hprof,formatb 3307…...

python16行代码获取原神全角色+全语音

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 本来是不玩原神的&#xff0c;但是实在是经不住双重诱惑呀~ 毕竟谁能拒绝角色风景超级好看又可以爬树、炸鱼、壶里造房子、抓小动物、躲猫猫的游戏捏~ 今天点进官网~角色得配音让我沉陷其中&#xff0c;于是 我决定把他们爬…...

链接投票二维码制作制作投票链接视频选举投票制作

关于微信投票&#xff0c;我们现在用的最多的就是小程序投票&#xff0c;今天的网络投票&#xff0c;在这里会教大家如何用“活动星投票”小程序来进行投票。我们现在要以“信赖挚友”为主题进行一次投票活动&#xff0c;我们可以在在微信小程序搜索&#xff0c;“活动星投票”…...

HTTP 协议

HTTP&#xff08;hypertext transport Protocol&#xff09;&#xff1b;超文本传输协议&#xff0c;是浏览器与万维网服务器之间通信的规则。 规定了客户端与服务端之间互相发送内容的格式&#xff0c;客户端发给服务端的叫 请求协议&#xff0c;服务端返回给客户端的为 响应…...

公司新招了个人,一副毛头小子的样儿,哪想到是新一代卷王····

内卷&#xff0c;是现在热度非常高的一个词汇&#xff0c;随着热度不断攀升&#xff0c;隐隐到了“万物皆可卷”的程度。 在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是…...

TSDF学习记录

【唐宇迪】三维重建-TSDF通俗解读 人工智能入门教程 水泡动画模拟&#xff08;Marching Cubes&#xff09; - 算法小丑 - 博客园 (cnblogs.com) TSDF 流程分析 首先需要构建一大块空区域采用体素网格来存储该区域需要计算每个体素的TSDF值及其权重 原理简述 SDF值&#x…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...

Java并发编程实战 Day 11:并发设计模式

【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天&#xff0c;今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案&#xff0c;它们不仅提供了优雅的设计思路&#xff0c;还能显著提升系统的性能…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...

Java中HashMap底层原理深度解析:从数据结构到红黑树优化

一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一&#xff0c;是基于哈希表的Map接口非同步实现。它允许使用null键和null值&#xff08;但只能有一个null键&#xff09;&#xff0c;并且不保证映射顺序的恒久不变。与Hashtable相比&#xff0c;Hash…...

第14节 Node.js 全局对象

JavaScript 中有一个特殊的对象&#xff0c;称为全局对象&#xff08;Global Object&#xff09;&#xff0c;它及其所有属性都可以在程序的任何地方访问&#xff0c;即全局变量。 在浏览器 JavaScript 中&#xff0c;通常 window 是全局对象&#xff0c; 而 Node.js 中的全局…...