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

news2024/12/26 9:23:23

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

文章目录

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

文章参考:

追忆四年前:一段关于我被外企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作为Key
    String 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限流模块:基于漏桶思想的限流模块,实现网关层的限流控制。
  • ........

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385720.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

01 | n2n虚拟局域网

1 n2n简介 为了满足两个不同局域网的机器进行通信&#xff0c;让不同网段的机器能够进行P2P( 点对点 peer-to-peer ) 通信。2 n2n源码 https://github.com/ntop/n2n.git3 n2n名词 3.1 SuperNode 超级节点 SuperNode 相当与注册中心, 它会记录边缘节点的连接信息&#xff0c;…

案例09-数据类型不一致导致equals判断为false

一&#xff1a;背景介绍 在判断课程id和班级id的时候如果一致就像课程信息进行更新&#xff0c;如果不一致就插入一条新的数据。其实两个变量的值是一致的但是类型是不一致的。这就导致数据库中已经有一条这样的数据了&#xff0c;在判断的时候结果为false&#xff0c;就有插入…

【这一篇就够】mysql创建JSON数据的索引

一. 创建索引 由于json有两类不同的数据形式&#xff0c;即&#xff1a;json对象&#xff08;如&#xff1a;{"id": 1, "name":"he"}&#xff09;&#xff0c;json数组&#xff08;如&#xff1a;["1","2","3"]&…

nexus安装与入门

安装 nexus-3.31.1-01-unix.tar.gz 链接&#xff1a;https://pan.baidu.com/s/1YrJMwpGxmu8N2d7XMl6fSg 提取码&#xff1a;kfeh 上传到服务器&#xff0c;解压 tar -zvxf nexus-3.31.1-01-unix.tar.gz进入bin目录&#xff0c;启动 ./nexus start查看状态 ./nexus status默…

初始Linux操作系统

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

Linux常用命令等

目录 1.Linux常用命令 (1)系统命令 (2)文件操作命令 2.vim编辑器 3.linux系统中,软件安装 (1) rpm 安装,RedHat Package Manager (2)yum 安装 (3)源代码编译安装 1.Linux常用命令 Linux命令是非常多的,对于像嵌入式开发工程师,运维工程师需要掌握的命令是非常多的.对于…

旋转矩形框标注--roLabelImg的使用

1. 旋转目标标注roLabelImg roLabelImg是一款开源的,可标注带旋转角度的矩形区域的标注软件。roLabelImg源码github地址: https://github.com/cgvict/roLabelImg labelme和labelimg只能标矩形框,但不能标注旋转角度。roLabelImg既能标矩形框也能标注矩形框的选择角度,因此…

复旦发布中国版 ChatGPT :MOSS

不知道这个人工智能&#xff0c;有没有获得完整的一生。ChatGPT 是最先进的 AI&#xff0c;也是最热门的应用 —— 自去年 11 月底发布以来&#xff0c;它的月活跃用户两个月超过一亿&#xff0c;轻松拿到了全球互联网史上用户增长速度的第一。它也是一种门槛很高的技术。由于 …

服务预热配置化在泛型化方法上的实践

背景 由于开发过程中&#xff0c;个别dubbo接口的调用会在服务发布过程中&#xff0c;出现P99耗时报警问题。因此我们计划通过预热服务接口&#xff0c;通过预热来触发JIT&#xff0c;构建DB资源长链接。实现服务接口上线后&#xff0c;耗时过长&#xff0c;资源等待等问题&am…

基于RWEQ模型的土壤风蚀模数估算及其变化归因分析

查看原文>>>基于RWEQ模型的土壤风蚀模数估算及其变化归因分析 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2&#xff…

python re模块匹配字符串

python 正则模块re 要使用python3中的RE则必须引入 re模块 import re re模块的match函数 result re.match(^[A-Z]{1}[a-z], s) match 尝试从字符串的起始位置匹配一个模式&#xff0c;如果不是起始位置匹配成功的话&#xff0c;match()就返回none。 匹配到了&#xff0c;则…

AI for Science系列(二):国内首个基于AI框架的CFD工具组件!赛桨v1.0 Beta API介绍以及典型案例分享!

AI for Science被广泛认为是下一代科研范式&#xff0c;可以有效处理多维度、多模态、多场景下的模拟和真实数据&#xff0c;解决复杂推演计算问题&#xff0c;加速新科学问题发现[1] 。百度飞桨科学计算工具组件赛桨PaddleScience是国内首个公开且可应用于CFD&#xff08;Comp…

通过工厂模式实现SpringBoot+MQTT-订阅与消费

引言 Spring Boot 是一款用于构建基于 Spring 框架的快速应用程序的开源框架。它的目标是简化 Spring 应用程序的开发和部署过程&#xff0c;Spring Boot 通过提供一些默认配置和自动配置&#xff0c;可以让开发者更快的创建一个独立的、产品级别的 Spring 应用程序。 MQTT 是…

pathon Django的关系映射

一对一 【创建】 一对一是表示现实事物间存在的一对一的对应关系。特殊字段选项 【必须】 on_delete - 级联删除 更多参考模型字段参考 | Django 文档 | Django使用oto示例&#xff1a; 1、先创建oto应用&#xff0c;然后到setting.py文件注册应用2、创建oto模型类3、创建…

【人工智能 AI】机器学习快速入门教程(Google)

目录 机器学习术语 标签 特性 示例 模型 回归与分类 深入了解机器学习&#xff1a;线性回归 深入了解机器学习&#xff1a;训练和损失 平方损失函数&#xff1a;一种常用的损失函数 机器学习术语 预计用时&#xff1a;8 分钟 什么是&#xff08;监督式&#xff…

蚁群算法再优化:combine aco algorithm with Sarsa in RL

蚁群算法再优化&#xff1a;combine aco algorithm with Sarsa in RL蚁群算法、Sarsa介绍和TSP问题介绍TSP和Sarsaaco algorithm具体的改进和代码改进说明部分代码数值实验结论分析参考文献蚁群算法、Sarsa介绍和TSP问题介绍 在进行蚁群算法优化介绍之前&#xff0c;笔者先将涉…

Apache Pulsar 云原生消息中间件之王

一、简介 pulsar&#xff0c;消息中间件&#xff0c;是一个用于服务器到服务器的消息系统&#xff0c;具有多租户、高性能等优势。 pulsar采用发布-订阅的设计模式&#xff0c;producer发布消息到topic&#xff0c;consumer订阅这些topic处理流入的消息&#xff0c;并当处理完…

OIDC OAuth2.0 认证协议最佳实践系列 02 - 授权码模式(Authorization Code)接入 Authing

在上一篇文章OIDC & OAuth2.0 认证协议最佳实践系列 02 - 授权码模式&#xff08;Authorization Code&#xff09;接入 Authing中&#xff0c;我们整体介绍 OIDC / OAuth2.0 协议&#xff0c;本次我们将重点围绕授权码模式&#xff08;Authorization Code&#xff09;以及接…

RabbitMQ第一讲

目录 一、RabbitMQ-01 1.1 MQ概述 1.2 MQ的优势和劣势 1.2.1 优势 1.2.2 劣势 1.2.3 MQ应用场景 1.2.4 常用的MQ产品 1.3 RabbitMQ的基本介绍 1.3.1 AMQP介绍 1.3.2 RabbitMQ基础架构 1.3.3 RabbitMQ的6种工作模式 ​编辑 1.4 AMQP和JMS 1.4.1 AMQP 1.4.2 JMS …

00后跨专业学软件测试,斩获8.5K高薪逆袭职场

我想说的第一句&#xff1a;既然有梦想&#xff0c;就应该去拼搏还记得&#xff0c;我大学毕业前&#xff0c;就已经暗下决心到xxx培训机构接受培训。那个时候&#xff0c;没有任何海同公司的人主动找我或者联系过我&#xff0c;我是自己在网上发现了xxxx培训机构的&#xff01…