限流的概念,算法,分布式限流以及微服务架构下限流的难点

2022-12-11 0 360

开闭基本概念

目的

透过对mammalian/允诺展开速度限制来为保护控制系统,防止控制系统网络连接。 努力做到有辱服务项目,而不是不服务项目。 阻抗过高时,优先选择为保护核心服务项目或销售业务

开闭形式

开闭的形式有很多:

QPS:管制每秒钟的允诺数 mammalian数:避免迈入过多线程导致资源用尽 通话量:管制TCP通话量

以下指的开闭都是指QPS的管制,不牵涉mammalian数和通话量。

开闭准则

开闭思路是依照销售业务特点来增设的,能增设动态思路,也能变化趋势的。

准则包涵有:

开闭演算法 模块,开闭共振频率 相应思路等(作法等)

动态准则

开闭共振频率

服务项目端提前做好整座服务项目端信道的性能银穗草,先了解销售业务特点,须要允诺API的QPS,估计好整座服务项目软件产业的水势及上游水势(上游服务项目和有关储存等开发工具)。服务项目端软件产业的开闭共振频率建立在银穗草的数据基础上。

银穗草须要得到的有关分项

服务项目:对于排序型服务项目,须要考量服务项目本身排序带来的控制计算资源耗用 内存及资料库:须要出访储存的服务项目,须要考量资料库的阻抗 最新消息堆栈:虽然一般消息堆栈能用来做为削峰商业用途,但是有的是最新消息堆栈是做为其它销售业务外交事务等商业用途的,也须要考量其阻抗情况 其它:上游信道中牵涉到的各种服务项目或者开发工具,都如果考量到其阻抗

须要考量:

相同API的相同共振频率 整座信道牵涉到的服务项目共振频率:如果某一允诺信道须要允诺10个服务项目,则共振频率排序须要考量这10个服务项目能够提供给此服务项目的网络流量进口产品 相同的开闭思路有相同的开闭共振频率 理论上每天API同时实现的销售业务方法论的改变,和增加、减少API都要展开重新银穗草,相对比较麻烦事 共振频率的估计如果考量整座服务项目的安全水势,共振频率表述过高则容易产生网络连接,截叶则污染环境

动态准则

动态准则的两种同时实现:

自适应:服务项目依照RT,阻抗,QPS等等分项动态更改开闭准则 外部通告:透过初始化服务项目API来通告服务项目预览或服务项目自己HTTP准则服务项目器

开闭共振频率

依照响应时间或在监视控制系统和服务项目环境治理中心其内依照服务项目及上游的阻抗来动态排序共振频率

服务项目端开闭 vs 客户端开闭 vs 网关开闭

绝大多数情况下开闭都是发生在服务项目端的,因为很多情况下客户端的数量是不确定的。但有时候为了防止单个客户端过度使用服务项目,那么此处能在客户端来完成,当然在服务项目端也能同时展开。

一般都推荐在服务项目端做开闭

服务项目端开闭

服务项目在处理允诺前,如果对允诺展开开闭排序,防止控制系统网络连接。

同时也要考量到为相同的销售业务的客户端提供相同的开闭思路,不能因为某一销售业务的问题达到达到开闭共振频率而造成其它销售业务无法允诺服务项目。

服务项目端开闭优点

更好控制整个服务项目的阻抗情况:服务项目端的开闭共振频率不会因为客户端数量增加或减少而改变 方便对相同上游服务项目展开相同共振频率的开闭思路:能对相同的初始化者展开相同的开闭进口产品,也能给相同销售业务打上相同的tag再依照tag来开闭。

缺点

如果服务项目端只针对QPS开闭,而不考量通话量:服务项目在建连过程中也会产生一些资源耗用,而这些压力往往可能会成为瓶颈。特别是短连接,不断的建链过程会产生大量的资源耗用 如果服务项目端也针对通话量展开管制:则不好对相同信道或服务项目展开进口产品区分。容易造成某一销售业务或服务项目的连接过多而导致其它服务项目也被管制

客户端开闭

客户端初始化上游服务项目时,以每个服务项目软件产业的开闭进口产品对上游服务项目展开网络连接为保护。

优点

达到共振频率不会允诺服务项目端,避免服务项目端产生额外的资源耗用,如建立连接

缺点

客户端的数量的增加或减少须要重新排序每个客户端的开闭共振频率 客户端开闭可能出现bug,或者客户端阻抗均衡产生倾斜导致开闭失效 服务项目相同API相同开闭共振频率:上游服务较多,而每个服务项目的相同API有相同开闭进口产品,则客户端的开闭较为复杂

网关开闭

允诺透过网关来允诺服务项目端,在网关中对相同服务项目及相同的API展开开闭。

优点

能很好的为保护整座软件产业的阻抗压力,服务项目端数量增加或减少,则网关展开相应的共振频率调整即可 对相同的上游销售业务的服务项目增设相同的开闭进口产品和相同的开闭思路

缺点

须要网关资源 网关本身高可用性

开闭粒度

开闭的粒度能分为:

服务项目:对服务项目所有API展开统一的开闭思路 API:每个API会有相同的允诺信道,则相应会有相同的开闭思路(共振频率等) API模块:很多时候我们希望能够对某一热点数据中出访频次最高的 Top K 数据展开管制。例如:秒杀,大促等场景,不要因为某一商品的频繁出访引起的开闭导致其它商品无法出访。

服务项目粒度

一个服务项目提供一个统一的开闭的思路。

优点是非常简单,但很容易造成开闭失效,无法为保护服务项目本身及上游。

如:服务项目提供两种API,都是出访数据,两种API的查询语句并不一致,API1 查询非常复杂,资料库安全水势只能提供10/s的TPS,而对于API2,资料库能提供1000/s的TPS,这种情况下,如果按照服务项目粒度展开开闭,则只能提供10/s QPS的开闭共振频率。所以是非常不合理的。

API粒度

相同的API展开相同的开闭思路,这种形式相对复杂些,但是更为合理,也能很好的为保护服务项目。

要考量几种情况:

增加或减少API,则开闭思路要做相应的调整 API同时实现的改变:允诺处理同时实现变化则可能须要重新对开闭共振频率展开调整,避免因为增加一些销售业务方法论而导致服务项目本身或者上游服务项目网络连接。 大多数情况下,都如果展开API粒度的开闭,这样才能更好的为保护服务项目本身及服务项目的上游服务项目和开发工具,达到更好的开闭效果。

开闭的作法

如果达到了网络流量管制的共振频率,一般作法有:

直接返回错误码:能返回资源用尽或者busy等状态码,或者带backoff的重试 等待及重试:要注意不要超过允诺超时时间

开闭算法

固定窗口演算法Fixed window

透过维护一个单位时间内的计数值,每当一个允诺透过时,就将计数值加1,当计数值超过共振频率时,就进入开闭处理流程。如果单位时间已经结束,则将计数器清零,迈入下一轮的计数。

限流的概念,算法,分布式限流以及微服务架构下限流的难点

这种形式的缺点是:窗口是固定的,会存在两个窗口边界突发网络流量问题。当然,取决于窗口的大小,如果足够小,则这种问题是能忽略的

如下图:

时间窗口为1s,1s的管制共振频率是4,如果一个恶意用户在1s的最后的最后500毫秒发送3个允诺,在下一秒的前500毫秒发送3个允诺,那么实际在1秒内发送了6个允诺,就超过了网络流量的管制。
限流的概念,算法,分布式限流以及微服务架构下限流的难点

滑动窗口演算法Sliding window

将时间窗口在展开细化,分为N个小窗口,窗口以小窗口为最小滑动单位,这样就能避免在两个时间窗口之间产生毛刺现象。(当然在小窗口之间仍然会产生毛刺)

限流的概念,算法,分布式限流以及微服务架构下限流的难点

令牌桶演算法Token Bucket

令牌桶演算法是网络网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种演算法。

有3个阶段

令牌的产生:以r/s的速率将token放入bucket中,如果bucket中token数已到达bucket的容量b,则丢弃token 获
限流的概念,算法,分布式限流以及微服务架构下限流的难点

【图

token bucket特点

最常见的单机开闭演算法,开源组件很多,使用也简单 token bucket能应对短时间的突发网络流量。例如:bucket容量为10,速率为2/s,而允诺QPS为2/s,那么bucket中有8个tokens能用来应对突发网络流量。

漏桶演算法Leaky Bucket

图左边a:一个桶,不断的倒水进来,桶底下有个洞,按照固定的速率把水漏走,如果水进来的速度比漏走的快,桶可能就会满了,水就溢出了图右边b:类似于左边a,将允诺放入桶中,以固定的速率从桶中拿出允诺展开处理,当桶满了,允诺将被阻塞或直接拒绝服务项目
限流的概念,算法,分布式限流以及微服务架构下限流的难点

leaky bucket特点

不能很好应付突发的网络流量:例如,桶容量为10,允诺处理速率为2/s;允诺放入桶的速率为2/s,那么能容纳8个突发允诺放入桶中,其它等待或者丢弃。 桶能是普通堆栈形式,也能是优先选择堆栈:优先选择堆栈是很有必要的 放入桶中的允诺如果排序好其超时时间,如果允诺放入桶中到允诺被处理,已经超过允诺的超时间,则是没有意义的,反而阻塞了其它允诺。 和令牌桶区别是:令牌桶是固定速率往桶里放令牌,允诺来了取走令牌,桶空了,请求阻塞;漏桶是允诺来了往桶里放允诺,以固定速率取走允诺,桶满了,允诺阻塞。令牌桶允许突发网络流量,而漏桶是不允许的。

动态开闭演算法

前面的演算法都是以恒定速率展开开闭的(令牌桶以固定速率将令牌放入桶中,固定和滑动窗口管制共振频率都是固定的),这种最大的问题就是每天销售业务方法论更改,都要重新展开压力测试以排序出最新的共振频率。

在TCP拥塞控制演算法中,网络流量发送速率是跟网络环境有关的。那其实服务项目的开闭也能依照服务项目处理允诺的时延或阻抗等分项来展开动态调整。

预热期慢启动

类似于TCP的慢启动,表述一个启动时的管制共振频率,在表述的预热时间周期内逐步提升管制共振频率,直到周期结束达到表述的正常值。

这非常适合于服务项目本身或其依赖的储存等需要展开预热的场景。

能参考 guava

例如:

以令牌桶为例,开始时管制共振频率为4/s,预热时间为3/s,正常管制共振频率为7/s,那么就在3秒内,将管制共振频率每秒钟增加1最终达到7/s的共振频率。
限流的概念,算法,分布式限流以及微服务架构下限流的难点

依照响应时间

这种形式根据允诺响应时间来实时调整开闭的准则,相对较为合理。

允诺响应快则平滑调整增加共振频率,响应慢则减少共振频率,和表述一个最大安全共振频率。

关键在于如何量化快与慢:

依照银穗草得到服务项目的安全水势,估计出最大共振频率。 启动时增设一个保守的共振频率 – 与前一个时间窗口内响应时间比较,比之前快则能调高共振频率。

那么随着响应时间的变化,共振频率也在不断的变化中,共振频率的范围在[1, max_value]之间调整。

基于监视控制系统

如果服务项目是消耗CPU资源的排序型或者耗用IO资源的储存型等,则基于监视控制系统更为合理。

依照CPU,load,内存,IO等控制系统分项和允诺响应时间等销售业务分项综合考量,随着监视分项的变化动态改变开闭准则,这种开闭相对较为复杂,依照自己销售业务设计适合自己的排序形式。

如果是IO型,则IO分项的权重相对要高一些,如果是排序型,则CPU和Load要权重高一些。

分布式系统开闭

单节点开闭最大的问题是当服务项目节点动态添加或减少后,每个服务项目的开闭进口产品也要跟随动态改变。

如果服务项目节点增加了,而原来节点开闭进口产品没有减少,则上游服务项目就可能网络连接。

而分布式系统开闭则避免了这种问题,透过像redis软件产业或发票服务项目器这种取号的形式来管制某一资源的网络流量。

redis开闭

基于redis的单线程及原子操作特性来同时实现开闭功能,这种形式能同时实现简单的分布式系统开闭。但是redis本身也容易成为瓶颈,且redis不管是主从结构还是其cluster模式,都存在主节点故障问题。

方案1:固定窗口计数

将要管制的资源名+时间窗口为精度的时间戳 做为redis 的key,增设略大于时间戳的超时时间,然后用redis的incrby的原子特性来增加计数。

如果开闭的时间窗口以秒为单位,则

redis key : 资源名 + unix timestamp count : incrby key count expire 2 检查count是否达到共振频率
local key = KEYS[1] local limit = tonumber(ARGV[1]) local acquireCount = tonumber(ARGV[2]) local current = tonumber(redis.call(get, key) or “0”) if current + acquireCount > limit then return 0 else redis.call(“incrby”, key, acquireCount) redis.call(“expire”, key, “2”) return 1 end

这种方案存在的问题:

要和redis展开交互:时延较差 热点资源redis容易成为瓶颈 redis展开主从切换会导致

方案2:令牌桶

local function acquire(key, acquireTokens, currentTimeMillSecond) local rateLimiterInfo = redis.pcall(“HMGET”, key, “lastTimeMilliSecond”, “availableTokens”, “maxLimit”, “rate”) local lastTimeMilliSecond = rateLimiterInfo[1] local availableTokens = tonumber(rateLimiterInfo[2]) local maxLimit = tonumber(rateLimiterInfo[3]) local rate = rateLimiterInfo[4] local currentTokens = availableTokens; local result = -1 if (type(lastTimeMilliSecond) ~= boolean and lastTimeMilliSecond ~= false and lastTimeMilliSecond ~= nil) then local diffTime = currentTimeMillSecond – lastTimeMilliSecond if diffTime > 0 then local fillTokens = math.floor((diffTime / 1000) * rate) local allTokens = fillTokens + availableTokens; currentTokens = math.min(allTokens, maxLimit); end end if (currentTokens – acquireTokens >= 0) then result = 1 redis.pcall(“HMSET”, key, “lastTimeMilliSecond”, currentTimeMillSecond, “availableTokens”, currentTokens – acquireTokens) end return result end local key = KEYS[1] local acquireTokens = ARGV[1] local currentTimeMillSecond = ARGV[2] local ret = acquire(key, acquireTokens, currentTimeMillSecond) return ret

这种方案存在的问题:

要和redis展开交互:时延较差 热点资源redis容易成为瓶颈 redis展开主从切换会导致开闭失效 服务项目的时钟会有误差

发票服务项目器

上述redis方案,是将redis做为一种发票服务项目器,但是由于redis这种方案本身存在可用性问题(主从切换等),控制准则也比较简单,所以对于可用性要求比较高且准则复杂的需求,都选择自己开发服务项目器程序来做为发票服务项目器。

阿里开源的sentinel

发票服务项目器一般由一些服务项目进程组成一个或多个发票软件产业。

而服务项目通

发票准则

发票准则(开闭演算法)能储存到一致性储存或者资料库等,发票服务项目器定期预览或者监听通

也能透过其它服务项目来动态调整演算法和共振频率,然后通告发票服务项目器,也能发票服务项目器自己依照阻抗情况来排序。

发票服务项目器特点:

发票服务项目器可用性高:透过软件产业模式,且能持久化到资料库。 发票服务器阻抗均衡:服务项目从发票服务项目软件产业领票要注意发票服务项目器阻抗均衡,避免造成有的是发票服务项目器发票领完有的是却有大量剩余发票 发票服务项目器高性能:因为发票服务项目器的排序和储存都基于内存,所以性能不容易成为瓶颈 发票服务器一致性:类似于ID生成器,对于极高要求的场景,能定期将发票服务项目器发票的信息等展开持久化储存,故障时再从中展开恢复

微服务项目构架下开闭的症结

在微服务项目构架下,初始化信道很长,服务项目的初始化关系复杂,上游服务项目一个小的改动,将影响所有上游服务项目,还会产生叠加效应。

流控准则

微服务项目构架下,固定流控准则是不合适的,固定准则往往依照银穗草结果展开排序得来,而微服务项目构架下,信道上一个节点的变化都会导致固定准则的失效。

如:

服务某一信道为 A->B->C->D

而B做了一些销售业务方法论的更改,那么信道就有可能产生变化,导致之前的信道银穗草结果不准确,如果还按照之前的共振频率,则可能导致流控失效。

微服务项目构架下,如果采用动态准则,让服务项目自适应或者准则服务项目器来通告服务项目改变开闭准则

依照初始化信道的开闭准则

上游服务项目展开开闭时,也要考量给予上游服务项目的网络流量进口产品,否则很容易因为由于某一上游服务项目的故障,导致整座上游信道不可用。

如:

信道1:A->B->C->D

信道2:D->B->C

A如果故障,导致初始化B网络流量暴涨,那么初始化C的网络流量也会暴涨。这个时候信道2的D服务项目则会收到B或C的流控影响。

所以B和C如果依照给予信道1和信道2展开相同的进口产品,信道1达到阈值则对信道1的初始化展开开闭,不影响信道2的初始化。

考量初始化信道优先选择级

一般微服务项目场景会依照销售业务来表述其可用性权重。权重高的销售业务往往要优先选择保证其可用性。

那么就存在复杂初始化信道上,针对相同销售业务的允诺来展开开闭,服务项目压力过大时,优先选择保证重要的销售业务可运行。

如: 信道1:A->B->C->D

信道2:D->B->C

信道1的重要性高于信道2,那么如果C的阻抗升高时,C就要降低整体的开闭共振频率,那么就要降低信道2的开闭共振频率,牺牲信道2的可用性来保证信道1。

所以就须要将信道展开tag标识,服务项目C依照tag来区分信道。

总结

开闭演算法往往相对简单且开源方案很多。而症结在于如何选择适合自己的开闭演算法。 不管是单节点线路还是分布式开闭,都有各自的优缺点,须要依照自身销售业务特性、规模和构架复杂性来选择适合自己的方案。微服务项目构架下,开闭变得相当复杂,要考量清楚各种可能导致开闭失效的场景。

C++的令牌桶同时实现:https://github.com/ikenchina/ratelimit

博客:https://ikenchina.github.io/

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务