限流在不同场景的最佳实践

目录导读

  • 限流在不同场景的最佳实践
    • 1. 前言
    • 2. 为什么要限流
    • 3. 有哪些限流场景
      • 3.1 限流场景分类
      • 3.2 限流与熔断降级之间的关系
      • 3.3 非业务限流
      • 3.4 业务限流
    • 4. 有哪些限流算法
      • 4.1 计数器限流算法
      • 4.2 漏桶限流算法
      • 4.3 令牌桶限流算法
      • 4.4 滑动时间窗限流算法
      • 4.5 限流算法选型
    • 5. 限流扩展实践
      • 5.1 调用量限流扩展实践
      • 5.2 QPS限流扩展实践
    • 6. 参考资料

限流在不同场景的最佳实践

1. 前言

  • 网上的很多资料,要么在介绍限流算法、要么就在讲限流实现;要么只关注开源的Sentinel是怎么做限流的,要么只关注业务逻辑中的限流是怎么做的……很少有人把各种限流的业务场景讲清楚。
  • 前面介绍了熔断降级和限流的开源实践:熔断降级与限流在开源SpringBoot/SpringCloud微服务框架的最佳实践 ,里面加了太多开源代码的前置条件,阅读起来可能不那么顺畅,现在想抛开开源框架,单独讲讲限流在不同场景的最佳实践。
  • 技术都是为了解决特定业务场景的问题而产生的,技术发展的初心就是简洁优雅地解决特定业务问题。
  • 由于本人见识、经验有限,可能存在一些认知偏差,欢迎各位批评指正。

2. 为什么要限流

  • 先给大家说明下我理解限流的定义:为了避免在特定时间内给软件系统造成超出其承受能力的资源访问,而采取的一种防御技术手段。
  • 在讲为什么需要限流时,先给大家讲讲没有限流行不行? 答案是:行!本人曾经在一家电信领域的巨无霸公司工作,在项目初期就会和客户确认业务并发量,然后基于这些指标设计方案、报价,得到客户的认可后,性能指标等都会写入交付合同,然后就启动开发、测试、性能评估,确保完全符合客户需求之后,才会正式交付给客户。当然实际交付时,性能指标通常会超出合同列举的性能指标,但是超出部分是不做商务承诺的,出了问题由客户自负;或者客户加钱,我们扩容或者设计更优的技术方案来满足更高的并发要求。不做限流是因为项目是私部署到客户机房的,很有可能只是客户业务场景中很小的一个环节,是不太好评估如何做限流,以及限制什么资源的访问流量;
  • 那我们为什么要做限流呢? 还是先举个亲身案例来说明:本人还在一家金融保险领域的巨无霸公司工作过,主要是给客户提供SaaS服务。在正式提供服务前,我们会先和客户签订商务合同,同时约定并发量,超出时就我们就会无责限流。一旦客户有活动,需要提前几天和我们申请更高的并发(额外收费),我们收到申请后,就会评估并扩容,活动结束后,我们再移除扩容的资源。我们限流是因为服务依赖底层的GPU算力,而GPU又非常昂贵,基于成本考虑,我们没有做弹性扩缩容。因为我们必须保证其他客户的正常合理使用,所以需要按照合同约定对每个客户做精确的限流。

3. 有哪些限流场景

3.1 限流场景分类

  • 我总结的限流分类如下图所示:
    限流分类
  • 下面依次澄清下上图的限流场景概念:
    • 从大的场景划分,限流可分为非业务限流(又叫非功能限流,DFX特性 之可靠性设计 )和业务限流(又叫功能限流,后面就不专门解释了,下同)。

    • 非业务限流就是为了系统高可靠而存在,限制全部客户访问微服务的整体流量,与具体业务无关,与具体客户无关,因此非常适合做公共的限流模块,且只做QPS限流(更严格来说是并发限流,下同);由于与业务无关,也不适合做针对单个客户的调用量限流

    • 业务限流则是为了精细化业务场景的限流,保证在各种业务场景下都能正常运行,可分为单客户限流单渠道限流(后面均简称客户限流渠道限流)。客户限流是指限制客户端的访问,以保证服务的稳定;渠道限流是指限制服务对底层服务的访问,以保证本服务有足够的调配空间。客户限流就是为了避免客户的访问超过了系统的限制,从而让系统不稳定;渠道限流则是为了避免被渠道限制而主动采取的防御手段。如:本服务对接了多个渠道,而每个渠道都设置了每天的最大访问量,为了能够更好的使用而不超过渠道限制,那就需要对渠道调用量限流;

    • 客户限流渠道限流均包含了QPS限流调用量限流QPS限流对应并发控制,调用量限流则是对应特定时间段的数量控制。二者的差异是:QPS限流时效极短,调用量限流时效较长;且二者实现的技术细节差别较大;

    • 从部署方式来看,限流可分为单机限流集群限流,对比分析如下:

      限流实现机制特点优势不足代表框架
      单机限流仅对实例本身做限流1.可以不依赖外部组件存储限流数据,存取效率高;
      2.实例扩展非常便捷(如扩缩容);
      1.无法把控集群的限流。一旦实例流量不均衡,限流就是灾难;
      2.实例数量会直接影响限流效果,不利于弹性扩缩容;
      Guava/Resilience4j
      集群限流对集群的多个实例做整体限流1.集群限流理论上可以兼容支持单实例限流;
      2.可以很好地支持实例的各种负载均衡模式;
      3.通常会把限流的数据存储从实例中摘除,非常利于限流的弹性;
      1.存在外部存储依赖,执行的效率相对较低;
      2.把分散的限流数据汇聚到了集中的存储组件中,数据量暴增N倍(N为实例数量),有时甚至需要更换存储方案;
      3.限流的异常处理逻辑会因为实例、引入的组件而变得非常复杂;
      Sentinel/Redis

综上所述:

  1. 限流在大多场景下都是需要的,限流可以规避系统瓶颈问题,保护大部分客户的正常使用;
  2. 限流除了大家理解的常规的限制客户流量外,还可以变相地做内部的资源调配(如渠道限流);
  3. 虽然有单机限流集群限流,但是在实际微服务项目中,基本上都是基于集群限流,只有像独立的三方件(如:Nginx)才会使用单机限流
  4. 限流也并不是在所有场景下都是必须的,有些系统内的小业务模块,就没有必要单独做限流,因为外部的网关或者接入服务可能已经做了限流;

3.2 限流与熔断降级之间的关系

  • 个人理解的限流、熔断和降级的概念 :
概念处理终端处理措施具体指标恢复措施
熔断客户端当客户端发起请求时,一旦服务方响应比较慢或者发生了异常,其数值超过了阈值,
则客户端在后续的请求中就直接跳过请求服务端,直接响应预设的失败结果
1.按照一定的响应时限熔断;
2.按照异常的比例或者类型熔断;
一般支持半熔断状态,可自动恢复
降级服务端/客户端1.作为服务端,当资源紧张时,主动停掉部分不重要的服务,直接响应预设的异常数据给客户端;
2.作为客户端,作用类似于熔断,但是比熔断的作用范围要小。比如:熔断前会先变成半熔断状态,就可以认为是服务降级;
1.按照业务重要程度来区分自动或者手动恢复
限流服务端/客户端1.作为服务端,当客户在特定时间内的请求达到一定阈值时,直接响应超限的异常结果;
2.作为客户端,在特定时间内,达到服务端允许的调用阈值时,直接响应超限异常或者更换调用其他服务方;
1.服务端约定时间内的最大调用量限流,如:允许客户A每天调用/xx接口100万次;
2.服务端约定QPS限流,如:允许客户A最大的QPS为100;
3.客户端被约定时间内的最大调用量限流,如:每天被允许调用/xx接口500万次;
4.客户端被约定QPS限流,如:被允许的最大QPS为200;
自动恢复
  • 限流、熔断降级框架比较 :
场景支持情况对比HystrixResilience4jSentinelRedisGuava
作为服务端,接口被调用的天调用量限流
作为服务端,接口被调用的QPS限流
作为服务端,接口被客户ID调用的天调用量限流
作为服务端,接口被客户ID调用的QPS限流
作为客户端,调用第三方接口的天调用量限流
作为客户端,调用第三方接口的QPS限流
集群限流

总之,可以这样来理解限流与熔断、降级之间的关系:

  1. 限流主要是基于请求而言,阻断了超越系统承受能力的请求,属于被动防御;
  2. 熔断和降级则除了基于请求而言,还可以基于某个资源(如数据库资源、文件资源等)来做控制,除了可以被动防御外,还可以主动防御(如主动降级不重要的服务或者业务),从某种程度而言,熔断和降级是限流的外延(延伸和扩展);
  3. 一般把限流和熔断、降级放在一起比较,主要是基于非业务限流,如上表的框架比较亦如此;
  4. 基于业务的限流(如限制A客户最高并发是20,日最大调用量时10w,B客户最高并发是50,日调用量时30w)因为涉及到太多的业务规则和业务定制,不适合放在基础框架中去做,就适合做业务限流。如上图而言,非业务限流最终采用了sentinel,而业务限流则采用了redis。

3.3 非业务限流

  • 如上限流框架分析可知:非业务限流使用支持集群限流的Sentinel更优。
  • 非业务限流在SpringCloud框架下,主要分为SpringCloud-Gateway(WebFlux技术栈,底层是基于Netty的NIO)、SpringBoot-Web(Tomcat技术栈,底层是基于Servlet的BIO)两种技术栈。仅做限流实现时,只需要在Sentinel中配置资源URL限流参数即可。具体实现参见限流、熔断降级框架比较 。

注意:非业务限流一般只做QPS限流

3.4 业务限流

  • 如上限流框架分析可知:业务限流使用支持集群限流的Redis更优。
  • 业务限流,可分为QPS限流调用量限流,在SpringCloud框架下,主要是依赖所有微服务实例共享的Redis的高性能内存操作。具体实现参见限流、熔断降级框架比较 。

4. 有哪些限流算法

  • 业务限流策略分为QPS限流调用量限流QPS限流会涉及较为复杂的限流算法,调用量限流基本上只需要计数器限流算法即可。
  • 通常所说的限流算法可分为计数器限流算法漏桶限流算法令牌桶限流算法滑动时间窗限流算法
  • 基于前面的业务场景分析,无论是非业务限流还是业务限流,基本上都是使用分布式限流算法。下面就仅以支持分布式限流的redis来分析上述各种限流算法。

4.1 计数器限流算法

  • 计数器限流算法是为了解决一定时间段内的总访问量问题。算法逻辑为:记录时间段的总调用量,一旦超过了阈值就限流。如:限制客户A每天的最大请求量为100笔。其redis的lua脚本代码如下:
    -- 获取限流key
    local limitKey = KEYS[1]
    --redis.log(redis.LOG_WARNING,'max limit key is:',limitKey)
    
    -- 调用脚本传入的限流大小
    local limitNum = tonumber(ARGV[1])
    
    -- 传入过期时间(ms)
    local expireMills = tonumber(ARGV[2])
    local count = redis.call('get',limitKey);
    if count then
        -- 获取当前流量大小
        local countNum = tonumber(count or "0")
        --是否超出限流值
        if countNum + 1 > limitNum then
            -- 拒绝访问
            return true
        else
            -- 没有超过阈值,设置当前访问数量+1
            redis.call('incrby',limitKey,1)
            -- 放行
            return false
        end
    else
        -- 没有超过阈值,设置当前访问数量+1
        redis.call('set',limitKey,1)
        -- 设置过期时间(ms)
        redis.call('pexpire',limitKey,expireMills)
        -- 放行
        return false
    end
    

4.2 漏桶限流算法

  • 漏桶限流算法是模拟漏斗同时进水和出水的场景来做限流的。算法逻辑:如果桶是空的,不用限流,如果桶满了,再来请求,则拒绝服务。它主要是保持恒定的流出速率,有流量整形的说法。但是桶的容量和流出速率设置是个比较麻烦的事情,而且它只保证了流出速率,无法100%保证QPS限流的效果。代码实现可参见计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现 。

4.3 令牌桶限流算法

  • 令牌桶限流算法则是模拟排队取号办业务场景来做限流的。它主要是保证了可控的流入速度,可避免突发高并发流量。与漏桶限流算法控制恰恰相反,令牌桶限流算法控制了请求的进入速度,漏桶限流算法控制了请求被处理的速度。代码实现可参见计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现 。

4.4 滑动时间窗限流算法

  • 滑动时间窗限流算法是表示在特定的时间内,汇总之前记录的各个时间点的访问数量,大于限流阈值就限流,否则就不限流。这个无论是理解还是实现都是最直观的。
  • 下图展示了在时间滑动时,2个时间窗(如编号①和③所示,假如每个时间窗都是1秒)的调用情况,按照固定时间窗来看,2个时间窗的QPS都是4。这其实就是固定时间窗限流算法,即:只关注整个单位时间内的调用数量。但是按照滑动时间窗来看,则还存在QPS为5的时间窗(如编号②所示)。理解滑动时间窗的核心是要理解:每个请求进入时,都需要往过去截取一个时间单位。
    滑动时间窗
  • 滑动时间窗限流算法的redis lua脚本实现如下:
    -- 获取限流key
    local limitKey = KEYS[1]
    --redis.log(redis.LOG_WARNING,'access limit key is:',limitKey)
    
    -- 调用脚本传入的限流大小
    local limitNum = tonumber(ARGV[1])
    -- 传入数据的有效期(ms,比如1秒)
    local expireMills = tonumber(ARGV[2])
    -- 传入当前时间(ms)
    local nowMills = tonumber(ARGV[3])
    
    --清除过期数据
    local expiredTimeMills = nowMills-expireMills;
    redis.call('zremrangebyscore',limitKey,0,expiredTimeMills);
    
    local count = redis.call('zcard',limitKey);
    
    -- 获取当前流量大小
    local countNum = tonumber(count or "0")
    
    --是否超出限流值
    if countNum + 1 > limitNum then
        -- 拒绝访问
        return true
    else
        -- 没有超过阈值,设置当前访问数量+1
        redis.call('zadd',limitKey,nowMills,nowMills)
        -- 设置过期时间(ms,相当于给这个zset key自动续期)
        redis.call('pexpire',limitKey,expireMills)
        -- 放行
        return false
    end
    

    前面之所以不介绍固定时间窗限流算法,就是因为它是滑动时间窗限流算法中的一个特例,即传入限流参数时,把时间戳对应的秒后面的毫秒数全部抹掉,补0就是了。大家可以想想这是为什么。

4.5 限流算法选型

  • 汇总下各限流算法的特点如下:
限流算法算法概述效率对比适用场景
计数器限流算法约定时间段内,基于计数限流算法简单,占用存储空间小适用于调用量限流,不能直接用于QPS限流
漏桶限流算法约定单位时间内,基于漏桶大小和流出速率来限流算法较简单,占用存储空间小适用于不严格,但需要稳定的限流速率的QPS限流场景
令牌限流桶算法约定单位时间内,基于令牌领取的排队机制来限流算法较复杂,占用存储空间较大适用于不严格,但是有突发高并发流量的QPS限流场景。如:抢购限流
滑动时间限流算法约定滑动的单位时间内,基于计数限流算法较复杂,占用存储空间大适用于严格,且无需关注超出并发的QPS限流场景

总之,无论是非业务限流,还是业务限流,在当下的微服务场景下,一般都建议使用集群限流。非业务限流建议使用Sentinel;业务限流则建议使用redis做限流。常规情况下,建议使用redis做精确的滑动时间窗限流;最好的方式还是结合上面的表格,基于具体场景去分析。

5. 限流扩展实践

  • 前面已经分析了限流的各个场景,以及各种限流算法实现。这里就重点讲讲怎么复用上面的限流实现去扩展支持各种不同的限流细化场景。

5.1 调用量限流扩展实践

  • 前面介绍了基于调用量限流的redis lua实现。实际工作中,我还碰到过做客户天调用量限流月调用量限流的限流。仍基于上面调用量的lua脚本中,传入不同的limitKeyexpireMills即可轻易做到。那是不是也很容易做到支持周、半个月、甚至是年的调用量限流?

5.2 QPS限流扩展实践

  • 前面介绍了基于QPS限流的redis lua实现。实际工作中,上面的QPS限流脚本是可以直接支持客户QPS限流和渠道QPS限流的。除此之外,还可以一行代码不改,扩展支持特定时间内的并发限流,如10秒内,并发不超过500个。也就是说QPS限流只是个人习惯的一个说法,其实用并发限流更准确,完全可以扩展到任意的时间单位来看并发。这也是上面的滑动时间窗图上没有标记时间单位的原因。

6. 参考资料

  • [1]限流算法:时间窗口,令牌桶与漏桶算法对比
  • [2]凤凰架构之流量控制
  • [3]限流算法-令牌桶、漏桶算法之java实现
  • [4]计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式 C 语言程序数据基本存储结构

一、5大内存分区 内存分成5个区,它们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 1、栈区(stack):FIFO就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。 ​…

Stable Diffusion AI绘图教学

课程介绍下载 这门课程将教授学生使用Stable Diffusion AI绘图工具进行数据可视化和图形设计。学生将学习基本的绘图原理、数据分析技巧,以及如何使用Stable Diffusion AI创建高质量的图表和可视化作品。通过实践项目和案例研究,学生将提升绘图技能&…

FlexRay汽车总线静电防护,如何设计保护方案图?

FlexRay是一种高速、实时、可靠、具备故障容错能力的总线技术,是继CAN和LIN总线之后的最新研发成果。FlexRay为线控应用(即线控驱动、线控转向、线控制动等)提供了容错和时间确定性性能要求。虽然FlexRay将解决当前高端和未来主流车载网络的挑…

.gitignore匹配规则

目录 1.直接一个名称2.斜杠 /3.符号 *4.问号 ?5.感叹号 !6.gitkeep 借鉴抖音账号: 渡一前端提薪课 1.直接一个名称 会忽略目录下的所有该名称文件和文件夹,无论嵌套多深。 2.斜杠 / 1.斜杠在开头(/dist):忽略和.gitig…

【Kubernetes】Kubernetes的调度

K8S调度 一、Kubernetes 调度1. Pod 调度介绍2. Pod 启动创建过程3. Kubernetes 的调度过程3.1 调度需要考虑的问题3.2 具体调度过程 二、影响kubernetes调度的因素1. nodeName2. nodeSelector3. 亲和性3.1 三种亲和性的区别3.2 键值运算关系3.3 节点亲和性3.4 Pod 亲和性3.5 P…

Python 图形界面框架TkInter(第八篇:理解pack布局)

前言 tkinter图形用户界面框架提供了3种布局方式,分别是 1、pack 2、grid 3、place 介绍下pack布局方式,这是我们最常用的布局方式,理解了pack布局,绝大多数需求都能满足。 第一次使用pack() import …

Web3.0:重新定义互联网的未来

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Web3.0:重新定义互联网的未来 Web3.0是指下一代互联网,也称为“分布式互联网”。相比于Web1.0和Web2.0,Web3.0具有更强的去中心化、…

LVS简介及LVS-DR搭建

目录 一. LVS简介: 1.简介 2. LVS工作模式: 3. LVS调度算法: 4. LVS-DR集群介绍: 二.LVS-DR搭建 1.RS配置 1)两台RS,需要下载好httpd软件并准备好配置文件 2)添加虚拟IP(vip&…

c++(空间配置器)[32]

空间配置器 一级空间配置器 || 二级空间配置器 默认先走二级然后判断 二级空间配置器 一个指针指向start_free然后start_free向后移动,相当于哈希桶的头删和头插 8byte:切大补小 C的二级空间配置器按照8字节(或者更大的倍数)切分…

网络安全---正则回溯

目录 一、题目引入 二、举出回溯例子进行分析 第一步: 正则往前匹配 第二步:匹配到头 第三步:往回匹配 第四步:直到分号结束 (匹配上) 原因: 三、进入正题一(分析题型&#…

【JavaWeb】实训的长篇笔记(下)

文章目录 八、功能实现1、注册功能2、登录功能3、问题说明4、首页数据显示5、后台管理 八、功能实现 1、注册功能 jsp:能够在页面中把数据动态化,jsp和html在元素标签上是无区别的,区别是html中写上java代码就成了jsp文件。filename.jsp。 需…

【Megatron-DeepSpeed】张量并行工具代码mpu详解(四):张量并行版Embedding层及交叉熵的实现及测试

相关博客 【Megatron-DeepSpeed】张量并行工具代码mpu详解(四):张量并行版Embedding层及交叉熵的实现及测试 【Megatron-DeepSpeed】张量并行工具代码mpu详解(三):张量并行层的实现及测试 【Megatron-DeepSpeed】张量并行工具代码mpu详解(一)&#xff1a…

安装paddleSeq2.7.0版本模块-笔记

安装paddleSeq2.7.0版本模块-笔记 先安装conda和python版本 本机安装的conda 22.9.0 python2.9.12 paddle2.4.2 paddlepaddle-gpu2.4.2 cuda10.2 安装matplotlib3.5.0版本 opencv_python-4.5.4.60-cp39-cp39-win_amd64.whl 测试采用分割模型名称:BiSeNetv2 #BiSe…

Android 项目导入高德SDK初次上手

文章目录 一、前置知识:二、学习目标三、学习资料四、操作过程1、创建空项目2、高德 SDK 环境接入2.1 获取高德 key2.2下载 SDK 并导入2.2.1、下载SDK 文件2.2.2、SDK 导入项目2.2.3、清单文件配置2.2.4、隐私权限 3、显示地图 一、前置知识: 1、Java 基…

Server - 文字转语音 (Text to Speech) 的在线服务 TTSMaker

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/132287193 TTSMaker 是一款免费的文本转语音工具,提供语音合成服务,支持多种语言,包括英语、法语、德语、西班…

每日一题——对称的二叉树

题目 给定一棵二叉树,判断其是否是自身的镜像(即:是否对称) 例如: 下面这棵二叉树是对称的 下面这棵二叉树不对称。 数据范围:节点数满足 0≤n≤1000,节点上的值满足 ∣val∣≤1000 要求&…

Rust 复数运算,重载加减乘除运算

复数 基本概念 复数定义 由实数部分和虚数部分所组成的数,形如a+bi 。 其中a、b为实数,i 为“虚数单位”,i 的平方等于-1。 a、b分别叫做复数a+bi的实部和虚部。 当b=0时,a&am…

Java8函数式编程

ISBN: 978-7-115-38488-1 作者:【英】Richard Warburton 页数:132页 阅读时间:2023-08-05 推荐指数:★★★★★ 练习项目:https://github.com/RichardWarburton/java-8-lambdas-exercises 虽然这本书出版于2014年&…

前端食堂技术周刊第 93 期:7 月登陆 Web 平台的新功能、Node.js 工具箱、Nuxt3 开发技巧、MF 重构方案

美味值:🌟🌟🌟🌟🌟 口味:橙橙冰萃美式 食堂技术周刊仓库地址:https://github.com/Geekhyt/weekly 大家好,我是童欧巴。欢迎来到前端食堂技术周刊,我们先来…

SQLyog中导入CSV文件入库到MySQL中

1.在数据库中新建一个表,设置列名(与待导入文件一致),字段可以多出几个都可以 2.右键表名,导入- - >导入使用本地加载的CSV数据 选择使用加载本地CVS数据 3.指定好转义字符,将终止设置为,号(英文状态下…