Sentinel 是怎样做限流的

计算机软件开发 2024-9-30 07:26:05 9 0 来自 中国
限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源举行限流可以有用地保障服务的可用性和稳固性。
之前的项目中利用的限流步调紧张是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,利用非常简单,但是功能相对比力少。
而现在,我们有了一种新的选择,阿里提供的Sentinel。
Sentinel 是阿里巴巴提供的一种限流、熔断中心件,与RateLimiter相比,Sentinel提供了丰富的限流、熔断功能。它支持控制台设置限流、熔断规则,支持集群限流,并可以将相应服务调用环境可视化。
现在已经有许多项目接入了Sentinel,而本文紧张是对Sentinel的限流功能做一次详细的分析,至于Sentinel的其他能力,则不作穷究。


一:总体流程:


1.png 从计划模式上来看,典范的的责任链模式。外部哀求进来后,要颠末责任链上各个节点的处置惩罚,而Sentinel的限流、熔断就是通过责任链上的这些节点实现的。
从限流算法来看,Sentinel利用滑动窗口算法来举行限流。要想深入相识原理,照旧得从源码上入手,下面,直接进入Sentinel的源码阅读。


二:源码解读:
1,总体流程:
读源码先得找到源码入口。我们经常利用@ SentinelResource来标志一个方法,可以将这个被@ SentinelResource标志的方法看成是一个Sentinel资源。因此,我们以@ SentinelResource为入口,找到其切面,看看切面拦截后所做的工作,就可以明白Sentinel的工作原理了。直接看注解@SentinelResource的切面代码(SentinelResourceAspect)。


可以清楚的看到Sentinel的举动方式。进入SentinelResource切面后,会实行SphU.entry方法,在这个方法中会对被拦截方法做限流和熔断的逻辑处置惩罚。
如果触发熔断和限流,会抛出BlockException,我们可以指定blockHandler方法来处置惩罚BlockException。而对于业务上的非常,我们也可以设置fallback方法来处置惩罚被拦截方法调用产生的非常。
所以,Sentinel熔断限流的处置惩罚紧张是在SphU.entry方法中,其紧张处置惩罚逻辑见下图源码。


可见,在SphU.entry方法中,Sentinel实现限流、熔断等功能的流程可以总结如下:
获取Sentinel上下文(Context);
获取资源对应的责任链;
生成资源调用凭据(Entry);
实行责任链中各个节点。
接下来,围绕这几个方面,对Sentinel的服务机制做一个体系的论述。
2,获取Sentinel上下文(Context)
Context,顾名思义,就是Sentinel熔断限流实行的上下文,包含资源调用的节点和Entry信息。

来看看Context的特性:
Context是线程持有的,利用ThreadLocal与当火线程绑定


Context包含的内容




这里就引出了Sentinel的三个比力紧张的概念:Conetxt,Node,Entry。这三个类是Sentinel的焦点类,提供了资源调用路径、资源调用统计等信息。
Context
Context是当火线程所持有的Sentinel上下文。
进入Sentinel的逻辑时,会起首获取当火线程的Context,如果没有则新建。当任务实行完毕后,会扫除当火线程的context。Context 代表调用链路上下文,贯穿一次调用链路中的全部 Entry。
Context 维持着入口节点(entranceNode)、本次调用链路的 当前节点(curNode)、调用泉源(origin)等信息。Context 名称即为调用链路入口名称。
Node
Node是对一个@SentinelResource标志的资源的统计包装。
Context中记载本当火线程资源调用的入口节点。
我们可以通过入口节点的childList,可以追溯资源的调用环境。而每个节点都对应一个@SentinelResource标志的资源及其统计数据,比方:passQps,blockQps,rt等数据。


Entry
Entry是Sentinel中用来表现是否通过限流的一个凭据,如果能正常返回,则阐明你可以访问被Sentinel掩护的后方服务,否则Sentinel会抛出一个BlockException。
别的,它生存了本次实行entry()方法的一些根本信息,包罗资源的Context、Node、对应的责任链等信息,后续完成资源调用后,还需要更具获得的这个Entry去实行一些善后利用,包罗退出Entry对应的责任链,完成节点的一些统计信息更新,扫除当火线程的Context信息等。


3.  获取@SentinelResource标志资源对应的责任链
资源对应的责任链是限流逻辑详细实行的地方,接纳的是典范的责任链模式。


默认的责任链中的处置惩罚节点包罗NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot、FlowSlot、DegradeSlot等。调用链(ProcessorSlotChain)和此中包含的全部Slot都实现了ProcessorSlot接口,接纳责任链的模式实行各个节点的处置惩罚逻辑,并调用下一个节点。
别的,雷同资源(@SentinelResource标志的方法)对应的责任链是划一的。也就是说,每个资源对应一条单独的责任链,可以看下源码中资源责任链的获取逻辑:先从缓存获取,没有则新建。


4. 生成调用凭据Entry
生成的Entry是CtEntry。其构造参数包罗资源包装(ResourceWrapper)、资源对应的责任链以及当火线程的Context。


7.png 可以看到,新建CtEntry记载了当前资源的责任链和Context,同时更新Context,将Context的当前Entry设置为本身。可以看到,CtEntry是一个双向链表,构建了Sentinel资源的调用链路。


5,责任链的实行
接下来就进入了责任链的实行。责任链和此中的Slot都实现了ProcessorSlot,责任链的entry方法会依次实行责任链各个slot,所以下面就进入了责任链中的各个Slot。为了突出重点,这次本文只研究与限流功能有关的Slot。


5.1      NodeSelectorSlot -- 获取当前资源对应Node,构建节点调用树
此节点负责获取大概构建当前资源对应的Node,这个Node被用于后续资源调用的统计及限流和熔断条件的判定。同时,NodeSelectorSlot还会完成调用链路构建。来看源码:


8.png

9.png 认识的代码风格。我们知道一个资源对应一个责任链。每个调用链中都有NodeSelectorSlot。NodeSelectSlot中的node缓存map黑白静态变量,所以map只对当前这个资源共用,差别的资源对应的NodeSelectSlot及Node的缓存都是不一样的,资源和Node缓存map的关系可见下图。


所以NodeSelectorSlot的的作用是:
在资源对应的调用链实行时,获取当前context对应的Node,这个Node代表着这个资源的调用环境。
将获取到的node设为当前node,添加到之前的node反面,形成树状的调用路径。(通过Context中的当前Entry举行)
触发下一个Slot的实行。
这里有个很风趣的标题,就是我们在责任链的NodeSelectorSlot中获取资源对应的Node时,为什么用的是Context的name,而不是SentinelResource的name呢?
起首,我们知道一个资源对应一条责任链。但是进入一个资源调用的Context却大概是差别的。如果利用资源名来作为key,获取对应的Node,那么通过差别context进来的调用方法获取到的Node就都是同一个了。所以通过这种方式,可以将雷同resource对应的node按Context区分开。
举个例子,Sentinel功能的实现不但仅可以通过@SentinelResource注解方法来实现,也可以通过引入相干依赖(sentinel-dubbo-adapter),利用Dubbo的Filter机制直接对DUBBO接口举行掩护。我们来比力@SentinelResource和Dubbo方式生成Context的区别:
Dubbo Filter方式
生成的context的name是Dubbo的接口限定名大概方法限定名。
如果出现嵌套在Dubbo Filter方式下面的其他SentinelResource的资源调用,那么这些资源调用的就会就会出现差别的Context。
所以有如许一种环境,差别的dubbo接口进来,这些dubbo接口都调用了同一个@SentinelResource标志的方法,那么这个方法对应的SentinelReource的在实行时对应的Context就是差别的。
另一个标题是,既然资源按Context分出了差别的node,那我们想看资源总数统计是怎么办呢?这就涉及到ClusterNode了。详细可见ClusterBuilderSlot。


5.2   ClusterBuilderSlot -- 聚合雷同资源差别Context的Node
此节点负责聚合雷同资源差别Context对应的Node,以供后续限流判定利用。


可以看到,ClusterNode的获取是以资源名为key。ClusterNode将会成为当前node的一个属性,紧张目标是为了聚条约一个资源差别Context环境下的多个node。默认的限流条件判定就是依据ClusterNode中的统计信息来举行的




5.3 StatisticSlot -- 资源调用统计
此节点紧张负责资源调用的统计信息的盘算和更新。与前面以及反面的slot差别,StatisticSlot的实行时先触发下一个slot的实行,等下面的slot实行完才会实行本身的逻辑。


这也很好明白,作为统计组件,总要等熔断大概限流处置惩罚完之后才气做统计吧。下面看一下详细的统计过程。


13.png

上面这张图已经很清楚的形貌了StatisticSlot的数据统计的过程。可以留意一下无非常和壅闭非常的环境,紧张是更新线程数、通过哀求数目和壅闭哀求数目。不管是DefaultNode,照旧ClusterNode,都继续自StatisticNode。所以Node的数据更新要来到StatisticNode。
参考Sentinel数据统计框图,形貌了Node统计数据更新的大要流程如下:




我们从StatisticNode.addPassRequest()方法入手,以passQps为例,探究StatisticNode是怎样更新通过哀求的QPS计数的。


15.png 从源码可见,计数变量rollingCounterInSecond和rollingCounterInMinute都是Metric,两个变量的时间维度分别是秒和分钟。rollingCounterInSecond和rollingCounterInMinute用的是Metric的实现类ArrayMetric。




从ArrayMetric追溯下去:
16.png 统计信息都是生存到ArrayMetric的data,也就是LeapArray<MertricBucket>中的。


LeapArray是时间窗口数组。根本信息包罗:时间窗口长度(ms,windowLengthInMs),取样数(也就是时间窗口的数目,sampleCount),时间隔断(ms,intervalInMs),以实时间窗口数组(array)。时间窗口长度、取样数实时间隔断有下面的关系:
windowLengthInMs = intervalInMs / sampleCount
代码中rollingCounterInSecond利用的intervalInMs 是1000(ms),也就是1s,sampleCount=2。所以,窗口时长就是windowLengthInMs = 500ms。rollingCounterInMinute利用的intervalInMs 是60 * 1000(ms),也就是60s。sampleCount=60,所以,windowLengthInMs = 1000ms,也就是1s。
时间窗口数组(array)是范例是AtomicReferenceArray,可见这是一个原子利用的的数组引用。数组元素范例是WindowWrap<MetricBucket>。windowWrap是对时间窗口的一个包装,包罗窗口的开始时间(windowStart)及窗口的长度(windowLengthInMs),以及本窗口的计数器(value,范例为MetricBucket)。窗口现实的计数是由MetricBucket举行的,计数信息是生存在MetricBucket里计数器counters(范例为(LongAdder))。可以看一下下图计数组件的构成框图:


回到StatisticNode.addPassRequest方法,以rollingCounterInSecond.addPass(count)为例,探究Sentinel怎样举行滑动窗口计数的。
5.3.1 获取当前时间窗口

(1)取当前时间戳对应的数组下标

long timeId = time / windowLength
int idx = (int)(timeId % array.length());
time为当前时间,windowLength为时间窗口长度,rollingCounterInSecond的时间窗口长度是500ms。array 是单元时间内时间窗口的数目,rollingCounterInSecond的单元时间(1s)时间窗口数是2。timeId是当前时间对时间窗口的整除。time每增长一个windowLength的长度,timeId就会增长1,时间窗口就会往前滑动一个。
(2)盘算窗口开始时间
窗口开始时间 = 当前时间(ms)-当前时间(ms)%时间窗口长度(ms)
获取的窗口开始时间均为时间窗口的整数倍。
(3)获取时间窗口
起首,根据数组下标从LeapArray的数组中获取时间窗口。
如果获取到的时间窗口自为空,则新建时间窗口(CAS)。
如果获取到的时间窗口非空,且时间窗口的开始时间便是我们盘算的开始时间,阐明当前时间恰幸亏这个时间窗口里,直接返回该时间窗口。
 如果获取到的时间窗口非空,且时间窗口的开始时间小于我们盘算的开始时间,阐明时间窗口已经逾期(隔断前次获取时间窗口已经已往比力久的场景),需要更新时间窗口(加锁利用),将时间窗口的开始时间设为盘算出来的开始时间,将时间窗口里的计数器重置为0。
 如果获取到的时间窗口非空,且时间窗口的开始时间大于我们盘算的开始时间,创建新的时间窗口。这个一样平常不会走进这个分支,由于阐明当前时间已经掉队于时间窗口了,获取到的时间窗口是将来的时间,那就没故意义了。


5.3.2 对时间窗口的计数器举行累加
时间窗口计数器是一个LongAdder数组,这个数组用于存放通过哀求数、非常哀求数、壅闭哀求数等数据。如下图:


此中,通过计数、壅闭计数、非常计数为实行StatisticSlot的entry方法时更新。乐成计数及相应时间是实行StatisticSlot的exit方法时更新。着实就是分别在被拦截方法实行前和实行后举行相应计数的更新。固然,addPass就是在计数数组的第一个元素上举行累加。
计数数组元素范例是LongAdder。LongAdder是JDK8添加到JUC中的。它是一个线程安全的、比Atomic*系工具性能更好的"计数器"。


5.4 FlowSlot -- 限流判定
FlowSlot是举行限流条件判定的节点。之前在StatisticSlot对相干资源调用做的统计,在FlowSlot限流判定时将会得到利用。
直接来到限流利用的焦点逻辑–限流规则查抄器(FlowRuleChecker):


20.png 紧张的流程包罗:
获取资源对应的限流规则
根据限流规则查抄是否被限流
如果被限流,则抛出限流非常FlowException。FlowException继续自BlockException。
那么FlowSlot查抄是否限流的过程是怎么样的?
默认环境下,限流利用的节点是当前节点的cluster node。紧张分析的限流方式是QPS限流。来看一下限流的关键代码(DefaultController):


21.png 获取节点的当前qps计数;
判定获取新的计数后是否高出阈值
高出阈值单返回false,表现被限流,反面会抛出FlowException。否则返回true,不被限流。
可以看到限流判定非常简单,只需要对qps计数举行查抄就可以了。这归功于StatisticSlot做的数据统计。


NodeSelectorSlot用于获取资源对应的Node,并构建Node调用树,将SentinelSource的调用链路以Node Tree的情势组起来。ClusterBuilderSlot为当前Node创建对应的ClusterNode,聚合雷同资源对应的差别Context的Node,后续的限流依据就是这个ClusterNode。

ClusterNode继续自StatisticNode,记载着相应资源处置惩罚的一些统计数据。StatisticSlot用于更新资源调用的相干计数,用于后续的限流判定利用。FlowSlot根据资源对应Node的调用计数,判定是否举行限流。至此,Sentinel的责任链实行逻辑就完整了。



6,Sentienl 的收尾工作
无论实行乐成照旧失败,大概是壅闭,都会实行Entry.exit()方法,来看一下这个方法。


判定要退出的entry是否是当前context的当前entry;
如果要退出的entry不是当前context的当前entry,则不退出此entry,而是退出context的的当前entry及其全部父entry,并抛出非常;
如果要退出的entry是当前context的当前entry(这种是正常环境),先退出当前entry对应的责任链的全部slot。在这一步,StatisticSlot会更新node的success计数和RT计数;
将context的当前entry置为被退出的entry的父entry;
如果被退出entry的父entry为空,且context为默认context,自动退出默认context(扫除ThreadLocal)。
扫除被退出entry的context引用




总结

通过阅读Sentinel的源码,可以很清楚的明白Sentinel的限流过程了,而对上面的源码阅读,总结如下:
三大组件Context、Entry、Node,是Sentinel的焦点组件,各类信息及资源调用环境都由这三大类持有;
接纳责任链模式完成Sentinel的信息统计、熔断、限流等利用;
责任链中NodeSelectSlot负责选择当前资源对应的Node,同时构建node调用树;
责任链中ClusterBuilderSlot负责构建当前Node对应的ClusterNode,用于聚条约一资源对应差别Context的Node;
责任链中的StatisticSlot用于统计当前资源的调用环境,更新Node与其对用的ClusterNode的各种统计数据;
责任链中的FlowSlot根据当前Node对应的ClusterNode(默认)的统计信息举行限流;
资源调用统计数据(比方PassQps)利用滑动时间窗口举行统计;
全部工作实行完毕后,实行退出流程,增补一些统计数据,整理Context。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:46, Processed in 0.129080 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表