目录

限流

怎么做限流?

获得系统能力上限、处理被限制流量

具体如何限流?

固定窗口

滑动窗口

漏桶

令牌桶

做限流的最佳实践

分布式系统中带来的新挑战


在软件架构领域,“限流”与“熔断”是两个经常会被同时提及的概念,它们都是系统高可用不可缺少的重要武器。

熔断是指在一个系统中,如果服务出现了过载现象,为了防止造成整个系统故障而切断服务的机制。它是一种十分有用的过载保护机制,一般会有下边这几种状态:

我们来考虑一个稍微极端一点的场景:如果系统流量不是很稳定,并且流量高峰时都会触发熔断,那么频繁的流量变化就意味着系统将一直在熔断的三种状态中不断切换。

这导致的结果是每次从开启熔断到关闭熔断的期间,大量用户将无法正常使用系统服务。这种情况下系统层面的可用性大致是这样的:

另外,资源利用率也很低,上图波谷的时间段资源都是未充分利用的。

由此可见,光有熔断是远远不够的。所以还需要限流机制。

限流

限流是对系统按照预设的规则进行流量限制的一种机制,它确保接收的流量不会超过系统所能承载的上限,以保证系统的可用性。与熔断不同,限流并不切断服务,因此服务会一直可用。

怎么做限流?

限制流量要限在哪个值好呢?

系统如果能将接收的流量持续保持在高位,但又不超过系统所能承载的上限,会是更有效率的运作模式,因为这会将前边提到的波谷填满。

也就是说限流最好能限在一个系统处理能力的上限附近,所以关于怎么做限流,第一步就是:通过压力测试等方式获得系统的能力上限在哪个水平。

除了获得这个限流的值,更主要的一步是具体怎么去限制这些流量,也就是制定限流策略,比如标准该怎么定、是只注重结果还是也要注重过程的平滑性等。

最后还需要考虑如何处理那些被限制了的流量,这些流量能不能直接丢弃?不能的话该如何处理?

获得系统能力上限、处理被限制流量

获得系统能力上限,简单地讲就是对系统做一轮压测。可以在一个独立的环境进行,也可以直接在生产环境的多个节点中选择一个节点作为样本来压测,当然需要做好与其它节点的隔离。

一般我们做压测是为了获得 2 个结果,速率和并发数。前者表示在单位时间内能够处理的请求数量,比如 xxx 次请求/秒,后者表示系统在同一时刻能处理的最大请求数量,比如 xxx 次的并发。从指标上需要获得最大值、平均值或者中位数,后续限流策略需要设定的具体标准数值就是从这些指标中来的。

此外,从精益求精的角度来说,诸如 CPU、网络带宽以及内存等资源的耗用也可以作为参照因素。

前边还讲到了做限流还要考虑触发限流后的措施,除了直接把请求流量丢弃之外,还有一种方式:“降级”。本文重点主要是在怎么具体去做限流,所以关于获得系统能力上限和这里的降级就不再继续展开了。

具体如何限流?

常用的策略就 4 种:固定窗口、滑动窗口、漏桶令牌桶

固定窗口

固定窗口就是定义一个固定的统计周期,比如 1 分钟或者 30 秒、10 秒这样,然后在每个周期统计当前周期中接收到的请求数量,经过计数器累加后如果达到设定的阈值就触发流量干预。直到进入下一个周期后,计数器清零,流量接收恢复正常状态。

这个策略最简单,写起代码来也没几行。

全局变量 int totalCount = 0;  //有一个「固定周期」会触发的定时器将数值清零。if(totalCount > 限流阈值) {return; //不继续处理请求。
}
totalCount++;// do something...

固定窗口有一点需要注意,假如请求的进入非常集中,那么设定的限流阈值等同于你需要承受的最大并发数。所以,如果需要考虑到并发问题,那么这里的固定周期设定得要尽可能短,因为,这样才能使限流阈值的数值相应地减小。甚至,限流阈值就可以直接用并发数来指定。比如,假设固定周期是 3 秒,那么这里的阈值就可以设定为平均并发数*3。

不过不管怎么设定,由于流量的进入往往都不是一个恒定的值,所以固定窗口永远存在一个缺点:流量进入速度有所波动,那么就会出现两种情况,要么计数器会被提前计满,导致这个周期内剩下时间段的请求被限制;要么就是计数器计不满,也就是限流阈值设定得过大,导致资源无法充分利用。

滑动窗口可以改善这个问题。

滑动窗口

滑动窗口其实就是对固定窗口做了进一步的细分,将原先的粒度切得更细,比如 1 分钟的固定窗口切分为 60 个 1 秒的滑动窗口。然后统计的时间范围随着时间的推移同步后移。

我们可以得出一个结论:如果固定窗口的固定周期已经很小了,那么使用滑动窗口的意义也就没有了。举个例子,现在的固定窗口周期已经是 1 秒了,再切分到毫秒级别反而得不偿失,会带来巨大的性能和资源损耗。

滑动窗口大致的代码逻辑是这样:

全局数组 链表[]  counterList = new 链表[切分的滑动窗口数量];
//有一个定时器,在每一次统计时间段起点需要变化的时候就将索引0位置的元素移除,并在末端追加一个新元素。int sum = counterList.Sum();
if(sum > 限流阈值) {return; //不继续处理请求。
}int 当前索引 = 当前时间的秒数 % 切分的滑动窗口数量;
counterList[当前索引]++;// do something...

虽然滑动窗口可以改善固定窗口关于周期设定的缺陷,但是本质上它还是预先划定时间片的方式,属于一种“预测”,也意味着它无法做到 100% 物尽其用。

桶模式可以做得更好,因为它多了一个缓冲区(桶本身)。

漏桶

漏桶模式的核心是固定“出口”的速率,不管进来多少量,出去的速率一直是这么多。如果涌入的量多到桶都装不下了,那么就进行流量干预。

整个实现过程我们来分解一下:

  1. 控制流出的速率。这个其实可以使用前面提到的两个窗口思路来实现,如果当前速率小于阈值则直接处理请求,否则不直接处理请求,进入缓冲区,并增加当前水位。
  2. 缓冲的实现可以做一个短暂的休眠或者记录到一个容器中再做异步的重试。
  3. 最后控制桶中的水位不超过最大水位。这个很简单,就是一个全局计数器,进行加加减减。

可以发现这其中的本质就是:通过一个缓冲区将高于均值的流量暂存下来补足到低于均值的时期,将不平滑的流量“整形”成平滑的,以此最大化计算处理资源的利用率

实现代码的简化表示如下:

全局变量 int unitSpeed;  //出口当前的流出速率。每隔一个速率计算周期(比如1秒)会触发定时器将数值清零。
全局变量 int waterLevel; //当前缓冲区的水位线。if(unitSpeed < 速率阈值) {unitSpeed++;//do something...
}
else{if(waterLevel > 水位阈值){return; //不继续处理请求。}waterLevel++;while(unitSpeed >= 速率阈值){sleep(一小段时间)。}unitSpeed++;waterLevel--;//do something...
}

这种更优秀的漏桶策略已经可以在流量总量充足的情况下发挥你预期的 100% 处理能力,但这还不是极致。

因为一个程序所在的运行环境中,往往不单单只有这个程序本身,还会存在一些系统进程甚至是其它的用户进程。也就是说,程序本身的处理能力是会被干扰的,是会变化的。所以,你可以预估某一个阶段内的平均值、中位数,但无法预估具体某一个时刻的程序处理能力。因此,你必然会使用相对悲观的标准去作为阈值,防止程序超负荷,这就使得资源的利用率不会达到极致。

那么从资源利用率的角度来说,有没有更优秀的方案呢?有,这就是令牌桶。

令牌桶

令牌桶模式的核心是固定“进口”速率。先拿到令牌,再处理请求,拿不到令牌就被流量干预。因此,当大量的流量进入时,只要令牌的生成速度大于等于请求被处理的速度,那么此刻的程序处理能力就是极限。

也来分解一下它的实现过程:

  1. 控制令牌生成的速率,并放入桶中。这个其实就是单独一个线程在不断地生成令牌。
  2. 控制桶中待领取的令牌水位不超过最大水位。这个和漏桶一样,就是一个全局计数器,进行加加减减。

大致的代码简化表示如下(看上去像固定窗口的反向逻辑):

全局变量 int tokenCount = 令牌数阈值; //可用令牌数。有一个独立的线程用固定的频率增加这个数值,但不大于「令牌数阈值」。if(tokenCount == 0){return; //不继续处理请求。
}tokenCount--;//do something...

但是这样一来令牌桶的容量大小理论上就是程序需要支撑的最大并发数。的确如此,假设同一时刻进入的流量将令牌取完,但是程序来不及处理,将会导致事故发生。

所以,没有真正完美的策略,只有合适的策略。因此,根据不同的场景选择最适合的策略才是更重要的。下面分享一些我选择这四种策略的经验。

做限流的最佳实践

固定窗口

一般来说,如非时间紧迫,不建议选择这个方案,它太过生硬。但是,为了能快速解决眼前的问题,那么它可以作为临时应急的方案。

滑动窗口

这个方案适用于对异常结果高容忍的场景,毕竟相比“两窗”少了一个缓冲区。但是,它胜在实现简单。

漏桶

我觉得这个方案最适合作为一个通用方案。虽说资源的利用率并不极致,但是宽进严出的思路在保护系统的同时还留有一些余地,使得它的适用场景更广。

令牌桶

当你需要尽可能地压榨程序的性能(此时桶的最大容量必然会大于等于程序的最大并发能力),并且所处的场景流量进入波动不是很大时(不至于一瞬间取完令牌,压垮后端系统),可以使用这个策略。

分布式系统中带来的新挑战

一个成熟的分布式系统大致是这样的:

每一个上游系统都可以理解为是其下游系统的客户端。然后我们回想一下前面的内容,可能你发现了,前面聊的限流都没有提到到底是在客户端做限流还是服务端做,甚至看起来更倾向是建立在服务端的基础上做。但是在一个分布式系统中,一个服务端本身就可能存在多个副本,并且还会提供给多个客户端调用,甚至其自身也会作为客户端角色。那么,在如此复杂的环境中,该如何下手做限流呢?我的思路是通过“一纵一横”来考量。

都知道限流是一个保护措施,那么可以将它想象成一个盾牌。另外,一个请求在系统中的处理过程是链式的。那么,正如古时候军队打仗一样,盾牌兵除了有小部分在老大周围保护,剩下的全在最前线。因为盾的位置越前,能受益的范围越大

分布式系统中最前面的是什么?接入层。如果你的系统有接入层,比如用 nginx 做的反向代理,那么可以通过它的 ngx_http_limit_conn_module 以及 ngx_http_limit_req_module 来做限流,这是很成熟的一个解决方案。

如果没有接入层,那么只能在应用层以 AOP 的思路去做了。但是,由于应用是分散的,出于成本考虑你需要针对性地去做限流。比如 To C 的应用必然比 To B 的应用更需要做,高频的缓存系统必然比低频的报表系统更需要做,Web 应用由于存在 Filter 的机制做起来必然比 Service 应用更方便。

那么应用间的限流到底是做到客户端还是服务端呢?

我的观点是,从效果上看客户端模式肯定是优于服务端模式的,因为当处于被限流状态的时候,客户端模式连建立连接的动作都省了。另一个潜在的好处是,与集中式的服务端模式相比,可以把少数的服务端程序的压力分散掉。但是在客户端做成本也更高,因为它是去中心化的,假如需要多个节点之间的数据共通的话,会是一个很麻烦的事情。

所以,我建议:如果考虑成本就选择服务端模式,考虑效果就选择客户端模式。当然也不是绝对,比如一个服务端的流量大部分都来源于某一个客户端,那么就可以直接在这个客户端做限流,这也不失为一个好方案。

数据库层面的话,一般连接字符串中本身就会包含最大连接数的概念,就已经可以起到限流作用了。如果想做更精细的控制就只能做到统一封装的数据库访问层框架中了。

聊完了纵,那么横是什么呢?

原文地址:https://my.oschina.net/editorial-story/blog/2999730

系统不做限流,我看你是对中国人口数量有什么误解相关推荐

  1. 【高并发】亿级流量场景下如何实现分布式限流?看完我彻底懂了!!(文末有福利)

    写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...

  2. 高并发系统处理之——限流

    高并发系统处理之--限流 对于高并发应用服务,有三个很好的方案可以保护系统 1.缓存 缓存的目的是提升系统访问速度和增大系统处理容量 2.降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉 ...

  3. Rabbitmq专题:rabbitmq消费端如何做限流?

    文章目录 1. 什么是消费端的限流? 2. 解决方案 3. 代码示例 1. 什么是消费端的限流? 场景:在订单高峰期,rabbitmq上已经堆积了很多消息等待消费,如果没有任何限流措施,贸然启动一个消 ...

  4. 1命名规则 sentinel_Sentinel实战:为系统做限流保护

    我们已经知道了 Sentinel 的三大功能:限流 降级 系统保护.现在让我们来了解下具体的使用方法,以限流来演示具体的步骤. 引入依赖 首先肯定是要先引入需要的依赖,如下所示: com.alibab ...

  5. 面试官 | 讲一下如何给高并发系统做限流?

    作者 | nick hao 来源 | uee.me/cDuRD 在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.本文结合作者的一些经验介绍限流的相关概念.算法和常规的实现方式. 缓存 缓存 ...

  6. 高并发系统中的限流应该如何做?

    缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪. 使用缓存不单单能够提升系统访问速度.提高并发访问量,也是保护数据库.保护系统的有效方式.大型网站一般主要是 ...

  7. 程序员修神之路--高并发优雅的做限流(有福利)

    点击上方蓝色字体,关注我们 菜菜哥,有时间吗? YY妹,什么事? 我最近的任务是做个小的秒杀活动,我怕把后端接口压垮,X总说这可关系到公司的存亡 简单呀,你就做个限流呗 这个没做过呀,菜菜哥,帮妹子写 ...

  8. 系统降级熔断限流和排队

    这类问题的主要原因在于系统压力太大.负载太高,导致无法快速处理业务请求,由此引发更多的后续问题.最常见的情况就是,数据库慢查询将数据库的服务器资源耗尽,导致读写超时,业务读写数据库时要么无法连接数据库 ...

  9. 【Guava】使用Guava的RateLimiter做限流

    2019独角兽企业重金招聘Python工程师标准>>> 一.常见的限流算法 目前常用的限流算法有两个:漏桶算法和令牌桶算法. 1.漏桶算法 漏桶算法的原理比较简单,请求进入到漏桶中, ...

最新文章

  1. 美多商城之支付(评价订单商品)
  2. 知识图谱前沿跟进,看这篇就够了,Philip S. Yu 团队发布权威综述,六大开放问题函待解决!...
  3. “ px”,“ dip”,“ dp”和“ sp”有什么区别?
  4. LaTeX 使用 bib 管理参考文献时,引用网络资源 URL 导致排版难看的问题
  5. 目标跟踪 facebook_如何关闭Facebook Messenger的位置跟踪(如果已启用)
  6. Netty实例-简单的服务端-client实现,凝视具体
  7. python 前端学习_python学习之路7 前端学习3
  8. session传递参数_JWT与Session的比较
  9. Dom4j工具--XML的DOM解析(下)--写操作
  10. python的*args与**kwargs
  11. plantuml最大宽度_设置TH最小和最大宽度非常缓慢
  12. python爬虫--爬取豆瓣top250电影名
  13. WebView的简单使用
  14. Windows 7 SP1开发完毕 已进入测试阶段
  15. GaN制备Micro-led(二)——光子晶体倒装 Micro-LED 制备的关键工艺(纳米压印光刻、干法刻蚀、介质薄膜沉积、物理气相沉积)
  16. 如何确认是文章发表在哪里?
  17. 2019-2-14SQLserver中拼音查询数据
  18. 真实骑手数据:73万大学毕业生在送外卖?
  19. 全排列牛客和L46,L47
  20. xmind 使用详解

热门文章

  1. python json库安装_jsonc库的安装以及简单使用
  2. python api加快交易速度_使用Python3的pipedriveapi将交易输出限制为1000个交易
  3. c语言程序设计实践教程编程题8.3,C语言程序设计教程(21世纪计算机科学与技术实践型教程)...
  4. java stdout库_Java重写StdOut并将日语写入文件
  5. 古典绘画水墨文化艺术插图手绘合集,再也不愁没有设计灵感!
  6. 「PPT模板」 商务UI风格
  7. python文件读写方法手机,python文本文件读写的3种方法
  8. C++ 处理异常相关
  9. Linux内核深入理解定时器和时间管理(5):clockevents 框架
  10. Linux文件系统概述:硬盘驱动>通用块设备层>文件系统>虚拟文件系统(VFS)