1、集群限流使用场景
首先一个服务有三个服务提供者,但这三台集群的硬件配置不一样,如图所示:
为了充分利用硬件的资源,诸如 Dubbo 都提供了基于权重的负载均衡机制,例如可以将8C16G的机器设置的权重是4C8G的两倍,这样充分利用硬件资源,假如现在需要引入 Sentinel 的限流机制,例如为一个 Dubbo 服务设置限流规则,这样由于三台集群分担的流量不均匀,会导致无法重复利用高配机器的资源。
假设经过压测,机器配置为C48G最高能承受的TPS为 1500,而机器配置为8C16G能承受的TPS为2800,那如果采取单机限流,其阔值只能设置为1500,因为如果超过1500,会将4C8G的机器压垮。
解决这种办法的方式就是针对整个集群进行限流,即为整个集群设置一个阔值,例如设置限流TPS为6000。
2、集群限流与单机限流的异同思考
限流的一个基本作用就是按照限流规则生成访问许可(Token),然后根据当前实时的调用信息进行判断是否可以获得许可而决定是否放行。
集群与单机限流在实时调用信息收集方面应该差别不大,都可以基于滑动窗口进行统计信息的收集。
集群与单机限流的最主要区别在与许可的生成,单机模式的许可直接在本地生成,但集群限流必须有一个统一的 Token 发放机制,以此来协调当前集群内多机调用,从而基于当前“调用总数”进行限流。
3、探究集群限流实现原理
在探究集群限流实现原理之前先来回顾一下单机限流的执行流程图。
结合流程我们可以看出集群限流的几个关键点 ClusterBuilderSlot、FlowSlot。
3.1 ClusterBuilderSlot 详解
在对一个资源进行流控规则判断时,首先将进入到 NodeSelectorSlot,然后就会进入到 ClusterBuilderSlot,为了与单机限流模式,介绍 ClusterBuilderSlot 时与 NodeSelectorSlot 进行一个对比。
NodeSelectorSlot 的核心实现截图如下所示:
温馨提示:从该系列之前的文章也能得知,一个 资源对应的一个 NodeSelectorSlot 实例,即多线程访问一个资源时,都会调用同一个 NodeSelectorSlot 实例。
NodeSelectorSlot 的关键点如下:
- Map map
在 NodeSelectorSlot 中是以 context Id 为维度进行缓存的,例如官方给出的 Dubbo 适配方法,contexId 为 dubbo 服务的全路径名。即 Dubbo的入口节点对应的缓存 Key 为 context
id。 - fireEntry 的 node 参数
由于 NodeSelectorSlot 是第一个过滤器,故第一次调用 fireEntry 方法时的 node 参数就是上面创建的 Node,即与 context 相关链的 Node,即所谓的入口节点即 Entrance Node。
接下来重点关注一下 ClusterBuilderSlot 的关键点:
ClusterBuilderSlot 的关键点如下:
- Map clusterNodeMap
持有的集群节点缓存表,其键为 Entrance Node 所对应的资源ID,即 Context 中关联的节点信息。 - Node originNode
所谓的 orginNode,即在调用 ContextUtil 中 enter(String name, String origin) 方法中的第二个参数,表示这条调用链的源头,在 Dubbo 中默认为 应用的 application。
经过上面两个Slot,整个调用链就基本创建好了,接下来我们来看一下 FlowSlot 关于集群限流的相关处理逻辑。
3.2 集群限流模式实现原理
FlowSlow FlowSlot 的核心处理逻辑主要是调用 FlowRuleChecker 的 canPassCheck 方法,正如上面看到的一样,根据配置规则,如果是集群模式,则调用的是其 passClusterCheck 方法,接下来我们将重点探讨该方法。
代码@1:获取一个 TokenService 服务类。这里实现关键点:
- 如果当前节点的角色为 CLIENT,返回的 TokenService 为 DefaultClusterTokenClient。
- 如果当前节点的角色为 SERVER,返回的 TokenService 为 ClusterTokenServer,这里使用了SPI极致,可以通过查看 META-INF/services 目录下的 com.alibaba.csp.sentinel.cluster.TokenService 文件,默认服务端返回 DefaultTokenService。
代码@2:如果无法获取到集群限流Token服务,如果该限流规则配置了可以退化为单机限流模式,则退化为单机限流。
代码@3:获取集群限流的流程ID,该 flowId 全局唯一。
代码@4:通过 TokenService 去申请 token,这里是与单机限流模式最大的差别。
接下来将分别从 DefaultClusterTokenClient、DefaultTokenService 分别探究集群限流相关的实现原理与细节,更好的指导我们如何使用集群限流功能。
3.2.1 DefaultClusterTokenClient 详解
从我们的经验也得知,TokenClient 的主要职责就是发送请求到 TokenService 端,主要是网络相关的细节将不在此篇文章中给出,如果有兴趣,大家可以关注我的 Netty 专栏。
首先 Sentinel 提供了 SPI 机制,故允许用户自定义 TokenClient 的实现类,官方与 SPI 默认配置的文件如下:
关于 TokenClient 主要关注其初始化代码,因为我们需要关注一个非常重要的点:
DefaultClusterTokenClient#initNewConnection
在客户端启动的时候会创建与 TokenServer 之间的链接,即这边需要配置服务端的 IP 与端口号,那如何配置呢?其实配置方式完全由自己去实现对应的解析器,下面根据官方的 Demo 示例如下:
这里需要说明的其配置项由 ClusterGroupEntity 来定义,其字段的定义如下:
- clientSet
客户端 Set 集合。 - ip
Token 服务端的 IP。 - machinedId
Token 服务端的机器ID。 - port
Token 服务端的机器端口。
其配置示例如下:
[{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}]
Client 端接下来就是向服务端发送请求,与网络相关的不在本文的讨论范围内,接下来将重点探讨服务端是如何发放许可的。
3.2.2 DefaultTokenService 详解
Token Server 端收到客户的请求,其处理入口为 FlowRequestProcessor,其处理方法为:processRequest,最终会调用 DefaultTokenService 的 requestToken 方法。
DefaultTokenService#requestToken
代码@1:根据 ruleId 获取指定的限流规则。
代码@2:然后调用 ClusterFlowChecker 的 acquierClusterToken 方法,申请许可。
许可的发放流程主要由 ClusterFlowChecker 的 acquierClusterToken 方法实现。
Step1:首先判断是否允许本次许可申请,这是因为 TokenServe 支持嵌入式,即支持在应用节点中嵌入一个 TokenServer,为了保证许可申请的请求不对正常业务造成比较大的影响,故对申请许可这个动作进行了限流。
一旦触发了限流,将向客户端返回 TOO_MANY_REQUEST 状态码,Sentinel 支持按 namespace 进行限流,具体由 GlobalRequestLimiter 实现,该类的内部同样基于滑动窗口进行收集,原理与 FlowSlot 相似,故这里不加以展开,默认的限流TPS为3W,有关于 Sentinel 相关的配置,将在后续文章专门梳理。
Step2:根据流程ID获取指标采集器。
Step3:计算 latestQps、globalThreashold、 nextRemaining 三个阔值,三个的含义分别如下:
- latestQps
获取当前正常访问的QPS。 - globalThreashold
根据限流配置规则得出其总许可数量,其主要根据阔值的方式其有所不同,其配置阔值有两种方式:
1)FLOW_THRESHOLD_GLOBAL
总数,即集群中的许可等于限流规则中配置的 count 值。
2)FLOW_THRESHOLD_AVG_LOCAL
单机分摊模式,此时限流规则中配置的值只是单机的 count 值,集群中的许可数等于 count * 集群中客户端的个数。
注意:这里还可以通过 exceedCount 设置来运行超过其最大阔值,默认为1表示不允许超过。 - nextRemainging
表示处理完本次请求后剩余的许可数量。
- Step4:如果剩余的许可数大于0,则本次申请成功,将当前的调用计入指标采集器中,然后返回给客户即可。
接下来所有流程步骤都是基于没有剩余许可数的处理逻辑。
Step5:当前许可数不足的情况,并且该请求为高优先级的处理逻辑:
- 获取当前等待的TPS(即1s为维度,当前等待的请求数量)
- 如果当前等待的TPS低于可借用未来窗口的许可阔值时,可通过,但设置其等待时间,可以通过 maxOccupyRatio 来设置借用的最大比值。
- Step6:如果当前许可不足,并且该请求为普通优先级的处理逻辑,增加阻塞相关指标的统计数,并返回 BLOCKED。
TokenServer 返回申请许可之后,那 Token Client 如何处理呢?其处理代码在 FlowRuleChecker#applyTokenResult
我们可以发现,如果服务端返回OK,则顺利通过,返回BLOCKED,则直接返回 false,会抛出 FlowException,如果是 token 限流,如果规则运行退化为单机限流模式,则进行单机限流。
集群限流的基本实现原理就介绍到这里了。
4、总结
集群限流的基本原理接介绍到这里了,与单机限流模式最大的区别就是集群限流模式的需要引入 TokenService,提供许可的发放服务,该服务可以嵌入应用节点,也可以独立于应用之外。这边借用官方文档上的两张图来简单介绍一下嵌入模式与独立模式的架构:
集群模式使用注意,如果使用的是集群模式限流,则如下两个配置则失效:
最后抛出一个思考题:集群模式应该算是高大上,但我们项目中真的需要吗?集群限流模式有哪些缺点、哪些优点,欢迎大家留言探讨。