通过 sentinel 的控制台,我们可以对规则进行查询和修改,也可以查看到实时监控,机器列表等信息,所以我们需要对 sentinel 的控制台做个完整的了解。
启动控制台
从github上下载源码后,启动sentinel-dashboard
模块。默认地址是8080。用户名和密码配置到了application.properties
中,可以自行修改,默认用户名和密码都是 sentinel
。
可以看到当前控制台中没有任何的应用,因为还没有应用接入。
接入控制台
要想在控制台中操作我们的应用,除了需要部署一个控制台的服务外,还需要将我们的应用接入到控制台中去。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
增加配置参数:控制台地址。
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
控制台使用懒加载,在第一次访问的时候才会开始进行初始化,并向控制台发送心跳和客户端规则等信息。
下面让我们对控制台的功能做具体的介绍。
流控规则
在簇点链路中可以配置流控规则,我们看下每个选项的含义。
- 资源名:默认使用请求路径,也可以修改成其他名称。
- 针对来源:可以对服务的调用来源进行限流。default为默认,会限流所有来源,也可以设置成其他服务名。
- 阀值类型:分为QPS和线程数,指的是到达限流的判断条件。
- 是否集群:设置单机还是集群限流,这个之后再讲解。
- 流控模式:
- 直接:指的是限流的目标就是当前的资源。
- 关联:需要填写关联资源。当关联资源的访问达到限流阀值,就会限制当前资源的访问。这是对关联资源的一种保护策略。
- 链路:这个是对来源更细粒度的配置。需要配置入口资源,也就是说从某个请求URL的入口进入才会进行流控判断。比如:/test-a 和 /test-b 都调用 /common这个资源。如果入口资源配置成 /test-a,那么 /test-b 不会进行流量控制。
- 流控效果:
- 快速失败:如果流控就抛出异常。
- Warm Up:先进行预热,根据codeFactor(默认是3)的值,从阀值/codeFactor,经过预热时长(秒)才到达设置的QPS阀值。
- 排队等待:匀速排队,让请求以均匀的速度通过,阀值类型必须设置成QPS,否则无效。需要设置超时时间,如果超出超时时间,请求才会被丢弃。
源码解析
流控是用FlowSlot
进行的判断。它也是责任链上的其中一个节点。我们知道这些节点的统一处理入口都是 entry
方法。
FlowSlot#entry
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
checkFlow(resourceWrapper, context, node, count, prioritized);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
throws BlockException {
checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}
FlowSlot在实例化的时候会实例化一个FlowRuleChecker实例作为checker。在checkFlow方法里面会继续调用FlowRuleChecker的checkFlow方法,其中ruleProvider实例是用来根据根据resource来从flowRules中获取相应的FlowRule。
我们进入到FlowRuleChecker的checkFlow方法中
FlowRuleChecker#checkFlow
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;
}
//返回FlowRuleManager里面注册的所有规则
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
//如果当前的请求不能通过,那么就抛出FlowException异常
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
这里是调用ruleProvider来获取所有FlowRule,然后遍历rule集合通过canPassCheck方法来进行过滤,如果不符合条件则会抛出FlowException异常。
我们跟进去直接来到passLocalCheck方法:
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);
}
这个方法里面会选择好相应的节点后调用rater的canPass方法来判断是否需要阻塞。
Rater有四个,分别是:DefaultController、RateLimiterController、WarmUpController、WarmUpRateLimiterController。它们是什么时候创建的呢?主要是调用了下面的这个方法。
FlowRuleUtil#generateRater
private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
switch (rule.getControlBehavior()) {
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
//warmUpPeriodSec默认是10
return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
ColdFactorProperty.coldFactor);
case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
//rule.getMaxQueueingTimeMs()默认是500
return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
default:
// Default mode or unknown mode: default traffic shaping controller (fast-reject).
}
}
return new DefaultController(rule.getCount(), rule.getGrade());
}
这个方法里面如果设置的是按QPS的方式来限流的话,可以设置一个ControlBehavior属性,用来做流量控制分别是:直接拒绝、Warm Up、匀速排队。
RateLimiterController匀速排队
它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。
这种方式适合用于请求以突刺状来到,这个时候我们不希望一下子把所有的请求都通过,这样可能会把系统压垮;同时我们也期待系统以稳定的速度,逐步处理这些请求,以起到“削峰填谷”的效果,而不是拒绝所有请求。
要想使用这个策略需要在实例化FlowRule的时候设置rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
这样的一句代码。
在实例化Rater的时候会调用FlowRuleUtil#generateRater
创建一个实例:
new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
MaxQueueingTimeMs默认是500 ,Count在我们这个例子中传入的是20。
我们看一下具体的canPass方法是怎么实现限流的:
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// Pass when acquire count is less or equal than 0.
if (acquireCount <= 0) {
return true;
}
// Reject when count is less or equal than 0.
// Otherwise,the costTime will be max of long and waitTime will overflow in some cases.
if (count <= 0) {
return false;
}
long currentTime = TimeUtil.currentTimeMillis();
//两个请求预期通过的时间,也就是说把请求平均分配到1秒上
// Calculate the interval between every two requests.
long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
//latestPassedTime代表的是上一次调用请求的时间
// Expected pass time of this request.
long expectedTime = costTime + latestPassedTime.