十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

2023-05-28 0 847

网络流量效用掌控,经典之作开闭演算法

开闭演算法借以掌控透过的网络流量仍旧在开闭共振频率内,相同开闭演算法还能掌控网络流量达至这种效用,如掌控超共振频率网络流量加速失利的计时器演算法、容许很大数目的允诺等候透过的漏桶演算法(Leaky Bucket),掌控网络流量慢速透过且容许网络流量在很大这种程度上科折粉的副本桶演算法(TokenBucket)。

1. 计时器演算法

Sentinel中预设同时实现的QPS开闭演算法和Threads开闭演算法都归属于计时器演算法。QPS开闭演算法是依照现阶段天数询问处(1秒)的pass(被离境的允诺数)分项统计数据推论的,当透过两个允诺时,现阶段天数询问处的pass分项算数加1,假如pass数目早已小于或等同于开闭的QPS共振频率,则间接婉拒现阶段允诺。Threads开闭演算法是依照现阶段天然资源博戈达挤占的缓存数与否早已达至共振频率推论的,若是,则间接婉拒现阶段允诺,因此每透过两个允诺,Threads算数加1,每顺利完成两个允诺,Threads算数减1。

2. 漏桶算法

漏桶就像在机壳的顶部开两个洞,不掌控水被放进桶的速度,而透过顶部安全漏洞的大小不一掌控水外流的速度。当水被放进桶的速度小于或等同于水透过顶部安全漏洞流入的速度时,桶中没余下的水;而当水被放进桶的速度小于水透过顶部安全漏洞流入的速度时,水就会渐渐在桶中累积,当桶木桶时,若再向桶中放进水,则放进的水就会外溢。

假如把水改成允诺,当处置允诺的速度小于或等同于允诺来临的速度时离境允诺,不然当允诺沉积到很大这种程度时,外溢的允诺将被婉拒,如图5.3所示。

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

图5.3 漏桶演算法

3. 副本桶演算法

副本桶不放置允诺,而要放置为允诺聚合的副本(Token),多于领到副本的允诺就能透过。副本桶演算法的基本原理是以一般来说速度向桶中放进副本,每每有

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

图5.4 副本桶演算法

网络流量效用控制器

Sentinel支持采取网络流量效用掌控器掌控超过开闭共振频率的网络流量。网络流量效用掌控器支持的功能包括间接婉拒、冷启动、慢速排队。

FlowRule的controlBehavior字段用于配置采用何种网络流量效用控制器。在调用FlowRuleManager#loadRules方法时,FlowRuleManager会将开闭规则配置的controlBehavior转换为对应的TrafficShapingController实例。

TrafficShapingController接口的源码如下:

node:依照limitApp与strategy选出来的Node(可为StatisticNode、DefaultNode或ClusterNode)。

acquireCount:与mammalian编程方法AQS#tryAcquire的参数作用一样,acquireCount的值一般为1。当开闭规则配置的开闭共振频率类型为Threads时,表示需要申请两个缓存;当开闭规则配置的开闭共振频率类型为QPS时,表示需要申请离境两个允诺。

prioritized:表示与否对允诺进行优先级排序,SphU#entry方法传递过来的值是false。

controlBehavior的取值与使用的TrafficShapingController的对应关系如表5.1所示。

表5.1 controlBehavior的取值与使用的TrafficShapingController的对应关系

加速失利网络流量效用掌控器

DefaultController是预设使用的网络流量效用掌控器,同时实现的效用是间接婉拒超过共振频率的允诺。当QPS超过开闭规则配置的共振频率时,新的允诺就会被立即婉拒,并抛出FlowException。

DefaultController适用于明确知道系统处置能力的情况,如透过压测确定共振频率。实际上我们很难测出这个共振频率,因为两个服务项目可能被部署在硬件配置相同的服务项目器上,因此随时都可能调整部署计划。

DefaultController#canPass方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

① avgUsedTokens方法:假如规则的开闭共振频率类型为QPS,则此方法返回Node统计的现阶段天数询问处早已放行的允诺数目;假如规则的开闭共振频率类型为Threads,则此方法返回Node统计的现阶段博戈达挤占的缓存数。

② 假如将现阶段允诺离境会超过开闭共振频率且不满足条件③,则间接婉拒现阶段允诺。

③ 假如prioritized为true且规则的开闭共振频率类型为QPS,则表示具有优先级的允诺能挤占未来天数询问处的统计分项。

④ 假如能挤占未来天数询问处的统计分项,则tryOccupyNext会返回现阶段允诺需要等候的天数,单位为毫秒。

⑤ 假如休眠天数在限制可挤占的最大天数范围内,则挂起现阶段允诺,令现阶段缓存休眠waitInMs毫秒,在休眠结束后抛出PriorityWaitException,标志现阶段允诺是等候了waitInMs毫秒之后透过的。在一般情况下,prioritized参数的值为false。假如prioritized在ProcessorSlotChain传递的过程中,排在FlowSlot之前的ProcessorSlot都没修改过,则条件③不会被满足,因此这个canPass方法同时实现的网络流量效用就是间接婉拒。

慢速开闭效用掌控器

Sentinel基于漏桶演算法并结合虚拟队列等候机制同时实现了慢速开闭效用。可将其理解为存在一个虚拟的队列,使允诺在队列中排队透过,每count / 1000毫秒可透过两个允诺。

使用虚拟队列的好处在于队列并非真实存在,当多核CPU博戈达处置允诺时,可能会出现这些允诺并排挤占两个位置的现象。也因为如此,实际透过的QPS会超过开闭共振频率的QPS,但不会超过很多。

若要使用慢速开闭效用掌控器RateLimiterController配置开闭规则,则必须配置开闭共振频率类型为GRADE_QPS,因此共振频率要小于或等同于1000。

配置使用慢速开闭效用掌控器的代码如下:

RateLimiterController类的字段和构造方法的源码如下:

maxQueueingTimeMs:允诺在虚拟队列中的最大等候天数,预设为500毫秒。

count:开闭共振频率的QPS。

latestPassedTime:最近两个允诺透过的天数,用于计算下两个允诺的预期透过天数。

RateLimiterController类同时实现的canPass方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

① 计算队列中连续两个允诺透过的天数间隔,假如开闭共振频率为200QPS,则costTime等同于5,即每5毫秒只容许透过两个允诺,而每5毫秒透过两个允诺就是一般来说速度。

② 计算现阶段允诺的期望透过天数,等同于天数间隔加上最近两个允诺透过的天数。

③ 假如期望透过天数少于或等同于现阶段天数,则现阶段允诺可立即透过。

④ 假如期望透过天数超过现阶段天数,则需要休眠等候,需要等候的天数等同于预期透过天数减去现阶段天数。

⑤ 假如等候天数超过队列容许的最大等候天数,则会婉拒现阶段允诺。

⑥ 假如更新latestPassedTime为期望透过天数后,需要等候的天数还是少于最大等候天数,则说明排队有效,不然说明在一瞬间被某个允诺占位了,需要婉拒现阶段允诺,将现阶段允诺移出队列并回退两个天数间隔。

⑦ 休眠等候waitTime毫秒。

latestPassedTime永远存储现阶段允诺的期望透过天数,后续的允诺将排在该允诺的后面,这就是虚拟队列的核心同时实现,按期望透过天数排队。在虚拟队列中,将latestPassedTime回退两个天数间隔,相当于将虚拟队列中的两个元素移除。

在理想情况下,每个允诺在队列中排队透过,则每个允诺都在一般来说的不重叠的天数透过。但在多核CPU的硬件条件下,可能出现多个允诺博戈达透过的情况,即队列中的两个位置被多个允诺同时挤占,这就是为什么说实际透过的QPS会超过开闭共振频率的QPS的原因。但是没关系,因并行导致超出的允诺数不会超过共振频率太多,所以影响不大。

慢速开闭效用掌控器适合用于允诺突发性增长后剧降的场景。例如,对于两个有定时任务调用的接口,在定时任务执行时允诺量会一下“飙高”,但随后又没了允诺,这时为了避免把系统压垮,我们不希望让所有允诺一下都透过,但也不想间接婉拒超过共振频率的允诺。在这种场景下,使用慢速开闭效用掌控器能将科折粉的允诺排队到低峰时执行,起到“削峰填谷”的效用。

在分析完源码后,我们再来看GitHub上Sentinel的两个Issue,如图5.5所示:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

图5.5 QPS共振频率超过1000后不生效的Issue

为什么将QPS开闭共振频率配置为超过1000后开闭就不生效了呢?

计算允诺透过的天数间隔的代码如下:

假设count=1200(开闭QPS共振频率),当acquireCount=1时,costTime=1000/1200,这个结果是小于1毫秒的,使用Math.round方法取整后值为1;而当QPS共振频率增大,costTime的计算结果小于0.5时,使用Math.round方法取整后值就变为0。

Sentinel支持的最小等候天数的单位是毫秒,这可能是出于性能的考虑。当开闭共振频率超过1000后,假如costTime的计算结果不小于0.5,那么天数间隔都是1毫秒,这相当于仍然开闭1000QPS;而当costTime的计算结果小于0.5时,使用Math.round方法取整后值为0,即允诺透过的天数间隔为0毫秒,也就是不排队等候,此时开闭规则就完全无效了。

冷启动开闭效用掌控器

Warm Up,即冷启动。在应用升级重启时,应用自身需要两个预热的过程,因为多于预热之后就能到达稳定的性能状态。在接口预热阶段能顺利完成JIT即时编译,顺利完成一些单例对象的创建、缓存池的创建,顺利完成各种连接池的初始化并执行首次需要加锁执行的代码块。

冷启动并非只在应用重启时需要,以下这些场景也需要冷启动:在一段天数内没访问的情况下,连接池中存在大量过期连接需要待下次使用才移除并创建新的连接、一些热点统计数据缓存过期需要重新查找统计数据库并写入缓存,等等。

WarmUpController支持设置冷启动周期,即冷启动的时长,预设为10秒。

WarmUpController掌控网络流量在冷启动周期内平缓地增长到开闭共振频率。

例如,某个接口开闭为200QPS,预热天数为10秒,那么在这10秒内,相当于每秒的开闭共振频率分别为5QPS、15QPS、35QPS、70QPS、90QPS、115QPS、145QPS、170QPS、190QPS、200QPS。当然,这组统计数据只是假设的。

假如要使用WarmUpController,则必须将开闭规则共振频率类型配置为GRADE_QPS,代码如下:

Sentinel的冷启动开闭演算法参考了Guava的SmoothRateLimiter的冷启动开闭演算法,但两者在同时实现上有很大的区别。Sentinel主要用于掌控QPS,不会掌控每个允诺的天数间隔。正因为与Guava有所相同,官方文档目前没很详细地介绍演算法的具体同时实现,单看源码很难揣摩作者的思路,因此我们也不过深地去分析WarmUpController的同时实现源码,只是结合Guava的同时实现演算法进行简单介绍。

Guava的SmoothRateLimiter基于副本桶(TokenBucket)演算法同时实现冷启动。

我们先看图5.6,从而了解SmoothRateLimiter中的一些基础知识。

stableInterval:稳定产生副本的天数间隔。

coldInterval:冷启动产生副本的最大天数间隔,等同于稳定产生副本的天数间隔乘以冷启动系数(即stableInterval×coldFactor)。

thresholdPermits:副本桶中余下副本数的共振频率,介于以正常速度生产副本还是以冷启动速度生产副本的共振频率,是推论与否需要进入冷启动阶段的依据。

maxPermits:容许副本桶中放置的最大副本数。

slope:直线的斜率。

warmupPeriod:预热时长,即冷启动周期,对应图5.6中梯形的面积。

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

图5.6 冷启动演算法

假设设置冷启动周期(warmupPeriod)为10s,开闭为每秒钟生产200个副本。

那么,用1秒(转换为微秒)除以每秒需要生产的副本数,计算出生产副本的天数间隔(stableInterval)为5000μs,对应SmoothRateLimiter的源码如下:

在SmoothRateLimiter中,冷启动系数(coldFactor)的值一般来说为3,所以冷启动阶段生产副本的最长天数间隔(coldInterval)等同于稳定速度下生产副本的天数间隔(stableInterval)乘以3,即15000μs。

由于coldFactor等同于3,且coldInterval等同于stableInterval乘以coldFactor,故(coldIntervalstableInterval)是stableInterval的两倍,因此从thresholdPermits到0的天数是从maxPermits到thresholdPermits天数的一半,也就是warmupPeriod的一半。

因为梯形的面积等同于warmupPeriod,所以点stableInterval与点thresholdPermits所在的长方形面积是梯形面积的一半,即长方形面积为warmupPeriod/2。

依照长方形的面积计算公式:面积=长×宽。

可得:stableInterval×thresholdPermits=0.5×warmupPeriod。

所以:thresholdPermits=0.5×warmupPeriod/stableInterval。

对应SmoothRateLimiter的源码如下:

因此,在本例中,thresholdPermits=0.5×10s/5000μs,计算结果为1000(个)。

依照梯形面积公式:(上底+下底)×高/2。

可得:warmupPeriod=((stableInterval+coldInterval)×(maxPermits-thresholdPermits))/2。

所以:maxPermits=thresholdPermits+2×warmupPeriod/(stableInterval+coldInterval)。

对应SmoothRateLimiter的源码如下:

因此,在本例中,maxPermits=1000+2.0×10s/(20000μs),计算结果为2000(个)。

依照直线的斜率计算公式:斜率=(y2-y1)/(x2-x1)。

可得:slope=(coldInterval – stableInterval)/(maxPermits – thresholdPermits)。

因此,在本例中,slope=10000μs/1000个,计算结果为101s/个。

在正常情况下,副本以稳定天数间隔stableInterval生产副本,1秒内能生产的副本就刚好是开闭的共振频率。

假设当初始化副本数为maxPermits时,系统间接进入冷启动阶段,此时生产副本的天数间隔最长,等同于coldInterval。

假如此时以稳定的速度消费副本桶中的副本,由于消费速度小于生产速度,那么副本桶中的副本将会慢慢减少;

当副本桶中的副本数慢慢下降到thresholdPermits时,冷启动周期结束,接下来将会以稳定的天数间隔stableInterval生产副本。当消费速度等同于生产速度时,副本数稳定在开闭共振频率;

而当消费速度远远小于生产速度时,副本桶中的副本就会沉积,假如沉积的副本数超过thresholdPermits,又会是一轮新的冷启动。

牌加入副本桶中。

在应用重启时或接口很久没被访问时,nextFreeTicketMicros的值要么为0,要么远远小于现阶段天数,所以现阶段天数与nextFreeTicketMicros的天数间隔非常大,导致第一次生产的副本数就达至了maxPermits,间接进入冷启动阶段。

SmoothRateLimiter#resync方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

在了解了Guava的SmoothRateLimiter同时实现后,我们再来看一下Sentinel的WarmUpController,其源码如下:

warningToken:等同于thresholdPermits,在稳定的副本生产速度下,副本桶中存储的副本数。

maxToken:等同于maxPermits,副本桶的最大容量。

storedTokens:副本桶现阶段存储的副本数。

lastFilledTime:上一次生产副本的天数。

coldFactor:冷启动系数,预设为3。

slope:斜率,每秒离境允诺数的增长速度。

count:开闭共振频率的QPS。

提示:warningToken、maxToken和slope的计算可参考Guava的SmoothRateLimiter。

WarmUpController#canPass方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

依照现阶段副本桶中存储的副本数超出warningToken的副本数,计算现阶段秒需要掌控的QPS的共振频率,下面这两行代码是关键:

我们能透过图5.7来理解这个公式。

结合图5.7能看出以下两点。

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

图5.7 Sentinel的冷启动演算法

图中的x1虚线的长度等同于aboveToken。

生产副本的间隔天数等同于y1的长度加上stableInterval,其在Sentinel中的单位为秒。

依照斜率和x1能计算出y1的值。

y1=slope×aboveToken。

而1.0/count计算出来的值是正常情况下每隔多少毫秒生产两个副本,即stableInterval。

所以,计算warningQps的公式等同于下面这行代码:

现阶段生产副本的天数间隔:aboveToken×slope+stableInterval=stableInterval+y1。

现阶段秒所能生产的副本数:1.0/(stableInterval+y1)。

所以,warningQps等同于当前秒所能生产的副本数。

Sentinel中的resync方法与SmoothRateLimiter的resync方法相同,在Sentinel中每秒只生产两个副本。WarmUpController的syncToken方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

Sentinel并不是每透过两个允诺就从副本桶中移除两个副本,而要在每秒更新副本桶的副本数时再扣除上一秒消耗的副本数,上一秒消耗的副本数等同于上一秒透过的允诺数,这就是官方文档所写的每秒会自动掉落副本。

这种做法避免了每次离境允诺都需要使用CAS更新副本桶中的副本数,能降低Sentinel对应用性能的影响,是一种非常巧妙的做法。

更新副本桶中的副本数=现阶段副本桶中余下的副本数+现阶段秒需要生产的副本数。

coolDownTokens方法的源码如下:

十年架构师带你深度解析微服务高并发限流:流量效果控制,很顶

其中,currentTime-lastFilledTime.get()为现阶段天数与上一次生产副本天数的天数间隔,虽然单位为毫秒,但是早已去掉了毫秒的部分(毫秒部分全为0)。

假如 currentTime-lastFilledTime.get()等同于1秒,则依照1秒等同于1000毫秒,新生产的副本数等同于开闭共振频率(count),新的副本桶中的余下副本数(newValue)等同于现阶段余下副本数(oldValue)加新生产的副本数。

newValue=oldValue+1000×count/1000=oldValue+count。

假如是在很久没访问的情况下,lastFilledTime远远小于currentTime,则第一次生产的副本数将等同于maxToken。

小结

本篇主要分析了Sentinel开闭功能的同时实现基本原理、can pass check全过程,以及网络流量效用掌控的同时实现基本原理,同时详细地介绍了Sentinel同时实现慢速开闭与冷启动效用所采用的演算法。

本文给大家讲解的内容是广度导出微服务项目高mammalian开闭:网络流量效用掌控

下篇文章给大家讲解的内容是深度导出微服务项目高并熔断降级 :旧版熔断降级

感谢大家的支持!

相关文章

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

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