@Author:zxw
@Email:502513206@qq.com
目录
1.前言
通过上篇文章已经知道FlowSlot
节点是用来处理我们流控的实现,以下为添加规则的示例代码
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
// 资源名
rule.setResource("HelloWorld");
// 限流类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 20.
// 限流阔值
rule.setCount(1);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
以下是Sentinel控制台提供给我们的配置界面
2.源码分析
2.1 FlowRule
在查看Sentinel是如何实现流控之前,首先对FlowRule
进行了解,单从实例代码来看一个FlowRule
对象就代表一条规则,而FlowRuleManager
则是我们规则的管理类了。先来看看Rule类的结构
可以发现有非常多的实现,不过对于流控的实现类则是FlowRule
对象。在Sentinel中资源是一切的根本,所以在AbstractRule
父类中定义了两个公共字段
private String resource;
private String limitApp;
对于limitApp
则是应用的来源,如果不存在的话,那么FlowRule
会提供一个默认的来源
public static final String LIMIT_APP_DEFAULT = "default";
public FlowRule() {
super();
setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
此外,在Sentinel有两种限流的方式,分别为qps和线程数量,在FlowRule
中使用grade字段进行标识,默认为qps限流。
// RuleConstant
public static final int FLOW_GRADE_THREAD = 0;
public static final int FLOW_GRADE_QPS = 1;
// FlowRule
private int grade = RuleConstant.FLOW_GRADE_QPS;
通过Sentinel控制台提供的功能来看,Sentinel有3种流控效果
- 直接失败
- warm up
- 排队等待
- 2与3的结合
默认情况,使用的是直接拒绝方式,通过controlBehavior
字段标识
public static final int CONTROL_BEHAVIOR_DEFAULT = 0;
public static final int CONTROL_BEHAVIOR_WARM_UP = 1;
public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2;
public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3;
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
配置了流控效果,那么就有不同的实现类去实现不同的流控策略,Sentinel通过TrafficShapingController
接口来实现不同的流控策略。
private TrafficShapingController controller;
所对应的实现则与上面配置的4个值相对应
既然是对流量进行控制,那么应该还需要记录当前流量值
private double count;
以上就是FlowRule
对象所涉及到的一些元数据,我们主要关注的字段有如下几个
- grade:配置qps或者线程数量模式
- controlBehavior:配置流控效果
- count:配置请求阈值
流控调度大概如下图
2.2 FlowRuleChecker
该类是规则检查类,所做的就是拿到该资源对应的所有规则,然后遍历并校验规则。
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
if (ruleProvider == null || resource == null) {
return;
}
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
在取到每一个规则后,调用canPassCheck
方法进行规则的校验,取到了资源的Node以后,获取到限流controller调用对应流控规则的限流算法
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
return true;
}
return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
3.限流算法
3.1 DefaultController(直接拒绝)
如果我们想使用并发线程数来进行流量控制,那么所使用的限流算法只能是DefaultController
,因为只有在该类中对并发线程数进行了判断处理。
首先思考一个问题,要想知道当前是否需要限流是不是首先得知道当前系统的调用程度。那么这里就会用到qps或者线程数量
int curCount = avgUsedTokens(node);
private int avgUsedTokens(Node node) {
if (node == null) {
return DEFAULT_AVG_USED_TOKENS;
}
return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
}
如果当前请求数小于限制的数量总数,那么我们直接放行即可
if (curCount + acquireCount > count) {
// .... 逻辑
return false;
}
return true;
不过Sentinel内部还有一个针对请求优先级的算法,有兴趣的可以自己研究
if (curCount + acquireCount > count) {
if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
long currentTime;
long waitInMs;
currentTime = TimeUtil.currentTimeMillis();
waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
node.addWaitingRequest(currentTime + waitInMs, acquireCount);
node.addOccupiedPass(acquireCount);
sleep(waitInMs);
// PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
throw new PriorityWaitException(waitInMs);
}
}
return false;
}
4.总结
这里只对直接拒绝策略的算法粗略的看了下,剩余其他算法感兴趣的可以直接研究,本篇主要分析了FlowRule
的元数据结构