热点参数限流
参数限流是指根据方法调用传递的参数实现限流,或者根据接口的请求参数限流,而热点参数限流是指对访问频繁的参数限流。
在电商场景中,每位顾客购买的商品不同,有主播带货的商品下单流量较大,而没有主播带货的商品相对来说下单流量较少。因为商品数量有限,每个下单请求都能成功是不太可能的,所以如果能够根据客户端请求传递的商品ID实现限流,将流量控制在商品的库存总量左右,并且使用QPS限流等进行兜底,即可将接口通过的有效流量最大化。
本篇内容主要包括以下几个方面:
热点参数限流功能的实现。
流量效果控制。
热点参数限流功能的实现
与围绕资源实现限流不同,热点参数限流是围绕资源的参数的不同取值来限流的,它不需要统计资源指标数据,而需要统计不同参数取值的指标数据。因此,热点参数限流功能不在Sentinel的核心模块中,而是被当作一个扩展功能放在Sentinel的扩展功能模块——sentinel-extension中,其子模块为sentinel-parameter-flow-control。
热点参数指标数据统计
热点参数限流不使用Node统计指标数据,而是使用ParameterMetric与ParameterMetricStorage统计指标数据。
ParameterMetric:用于实现类似于ClusterNode的统计功能。
ParameterMetricStorage:用于实现类似于EntranceNode的功能,管理和存储每个资源对应的ParameterMetric。
ParameterMetric有3个静态字段,源码如下:
ruleTimeCounters:用于实现匀速流量控制效果,key为参数限流规则(ParamFlowRule),value为参数不同取值对应的上次生产令牌的时间。
ruleTokenCounter:用于实现匀速流量控制效果,key为参数限流规则(ParamFlowRule),value为参数不同取值对应的当前令牌桶中的令牌数。
threadCountMap:key为参数索引,value为参数不同取值对应的当前并行占用的线程总数。
ParameterMetricStorage使用ConcurrentHashMap缓存每个资源对应的ParameterMetric,且只会为配置了参数限流规则的资源创建一个ParameterMetric实例,其部分源码如下:
ParameterMetricStorage的initParamMetricsFor方法用于为资源创建ParameterMetric实例并初始化。该方法在资源被访问时由ParamFlowSlot调用,并且该方法只在为资源配置了参数限流规则的情况下被调用。
热点参数限流的实现原理
既然是参数限流,那么肯定需要获取参数,而ProcessorSlot#entry方法的最后一个参数就是请求传递过来的参数,该参数通过SphU#entry方法一层层向下传递。
回顾Sentinel的使用案例,代码如下:
在此案例中,如果导入了热点参数限流模块,并且为资源GET:/hello配置了热点参数规则,则调用SphU#entry方法时传入的name就是热点参数,且name最终会被传递给热点参数限流模块的ProcessorSlot。
热点参数限流模块通过JavaSPI注册自定义的SlotChainBuilder,即注册HotParamSlotChainBuilder,将ParamFlowSlot放置在StatisticSlot的后面,这个ParamFlowSlot就是实现热点参数限流功能的处理器插槽,其部分源码如下:
如源码所示,在ParamFlowSlot#entry方法中,首先调用ParamFlowRuleManager#hasRules方法判断当前资源是否存在参数限流规则,如果存在,则调用ParamFlowSlot#checkFlow方法判断当前请求是否允许通过。ParamFlowSlot#checkFlow方法的源码如下:
checkFlow方法的最后一个参数是请求参数,也就是调用SphU#entry方法传递进来的参数,如果参数为null,则没有必要走后续逻辑。
调用ParamFlowRuleManager#getRulesOfResource方法获取为当前资源配置的所有参数限流规则。
遍历参数限流规则,首先调用ParameterMetricStorage#initParamMetricsFor方法判断是否需要为当前资源初始化创建ParameterMetric实例,然后调用ParamFlowChecker#passCheck方法判断当前请求是否可以被放行,如果需要拒绝请求,则抛出ParamFlowException。
在阅读ParamFlowChecker#passCheck方法的源码之前,我们需要先了解参数限流规则的配置,了解每个配置项的作用。
设置参数限流规则的类名为ParamFlowRule,其源码如下:
grade:限流规则的阈值类型,支持的类型与FlowRule相同。
count:阈值,支持的类型与FlowRule相同。
paramIdx:参数索引,ParamFlowChecker根据限流规则的参数索引获取参数的值,下标从0开始。例如,apiHello(String name)方法只有一个参数,索引为0对应name参数。
controlBehavior:流量控制效果,支持的类型与FlowRule相同,但只支持快速失败和匀速排队。
maxQueueingTimeMs:实现匀速排队流量控制效果的虚拟队列最大等待时间,超过该值的请求被抛弃,支持的类型与FlowRule相同。
durationInSec:统计指标数据的时间窗口大小,单位为秒。
burstCount:支持的突发流量总数。
假设需要对资源GET:/hello的name参数限流,当name取值为jackson时,QPS限流阈值为5,则代码如下:
以此为例,我们继续分析ParamFlowChecker#passCheck方法的源码。passCheck方法返回true表示放行,返回false表示拒绝。passCheck方法的源码如下:
若参数为空,或者规则配置的参数索引越界,或者参数索引对应的参数值为空,则放行请求。
若是集群限流模式,则调用passClusterCheck方法,否则调用passLocalCheck方法。
我们先不讨论集群限流情况,仅看单机本地限流情况。passLocalCheck方法的源码如下:
由于参数可能是基本数据类型,也可能是数组类型或引用类型,因此passLocalCheck方法分3种情况进行处理。这里只讨论其中一种情况,其他情况的处理方式与其类似。
以资源GET:/hello为例,其apiHello方法的name参数为String类型,因此会调用passSingleValueCheck方法。该方法的源码如下:
当规则配置的阈值类型为QPS时,根据限流效果调用passThrottleLocalCheck方法或passDefaultLocalCheck方法。
当规则配置的阈值类型为Threads时,获取当前资源的ParameterMetric实例,从而获取当前资源和参数取值对应的并行占用线程总数。如果并行占用线程总数加1大于限流阈值,则拒绝请求,否则放行请求。
并行占用线程总数是在哪里自增和自减的呢?这是由
ParamFlowStatisticEntryCallback与ParamFlowStatisticExitCallback这两个Callback实现的,这两个Callback分别在StatisticSlot的entry方法和exit方法中被回调执行。
本文给大家讲解的内容是深度解析微服务高并发热点参数限流:热点参数限流功能的实现
领取专属 10元无门槛券
私享最新 技术干货