前言:
上一篇我对 Sentinel 中的 ParamFlowSlot 的相关源码进行了分析,本篇我们开始分析 FlowSlot 相关的源码。
Sentinel 系列文章传送门:
Spring Cloud 整合 Nacos、Sentinel、OpenFigen 实战【微服务熔断降级实战】
Sentinel 源码分析入门【Entry、Chain、Context】
Sentine 源码分析之–NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot
Sentine 源码分析之–AuthoritySlot、SystemSlot、GatewayFlowSlot
FlowSlot 的规则配置
FlowSlot 的作用是进行流控,规则配置如下:
规则测试结果:
FlowSlot#entry 方法源码解析
FlowSlot#entry 方法只做两件事,执行限流规则校验,进入下一个 Slot。
//com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
//限流规则校验
checkFlow(resourceWrapper, context, node, count, prioritized);
//进入下一个 slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
FlowSlot#checkFlow 方法源码解析
FlowSlot#checkFlow 方法没有什么逻辑,直接调用了 FlowRuleChecker#checkFlow 方法。
//com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#checkFlow
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
throws BlockException {
//规则校验
checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}
FlowRuleChecker#checkFlow 方法源码解析
FlowRuleChecker#checkFlow 方法会对资源为空进行判断,然后获取资源的所有流控规则,遍历执行流控规则,我们重点关注 FlowRuleChecker#canPassCheck 方法。
//com.alibaba.csp.sentinel.slots.block.flow.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;
}
//获取资源的流控规则
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);
}
}
}
}
FlowRuleChecker#canPassCheck 方法源码解析
FlowRuleChecker#canPassCheck 方法会先获取流控资源的名称并对其进行为空判断,然后区分是否是集群模式,进行不同的处理。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#canPassCheck(com.alibaba.csp.sentinel.slots.block.flow.FlowRule, com.alibaba.csp.sentinel.context.Context, com.alibaba.csp.sentinel.node.DefaultNode, int, boolean)
public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
//获取限流资源名称
String limitApp = rule.getLimitApp();
if (limitApp == null) {
//限流资源为空 返回通过
return true;
}
//是否是集群模式
if (rule.isClusterMode()) {
//集群模式处理
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
//非集群模式
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
FlowRuleChecker#passClusterCheck 方法源码解析
FlowRuleChecker#passClusterCheck 方法是集群模式的处理方式,其主要做了一下几件事:
- 获取 TokenService,优先获取客户端再获取服务端。
- 使用 TokenService 请求令牌。
- 处理请求结果。
以上步骤都没有执行,判断是否需要回退到本地模式,模式是回退到本地模式。
TokenService有三个实现:
- DefaultClusterTokenClient:集群流控客户端,用于向所属 TokenServer 通信请求 token,集群限流服务端会返回给客户端结果,客户端决定是否限流。
- DefaultEmbeddedTokenServer:内嵌模式的集群服务端,作为内置的 TokenServer 和服务部署在同一进程中启动,用于处理客户端请求判断是否发放 token。
- DefaultTokenService:单独部署的集群服务端,用于处理客户端请求,判断是否发放 token。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#passClusterCheck
private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
try {
//获取执行规则集群节点 优先 客户端 然后 server
TokenService clusterService = pickClusterService();
if (clusterService == null) {
//集群节点获取结果为空 回退到本地模式或者通过
return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
}
//获取 flowId
long flowId = rule.getClusterConfig().getFlowId();
//clusterService 是 client 远程调用 如果 server 端 直接调用 获取集群令牌的结果
TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);
//处理获取到的令牌结果
return applyTokenResult(result, rule, context, node, acquireCount, prioritized);
// If client is absent, then fallback to local mode.
} catch (Throwable ex) {
RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);
}
// Fallback to local flow control when token client or server for this rule is not available.
// If fallback is not enabled, then directly pass.
//前面逻辑都没有处理到 回退到本地模式 或者 直接通过
return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
}
DefaultTokenService#requestToken 方法源码解析
DefaultTokenService#requestToken 方法首先对流控的请求有效性进行验证,然后获取流控规则,调用 ClusterFlowChecker.acquireClusterToken 方法得到流控结果。
//com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService#requestToken
@Override
public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
//请求有效性验证
if (notValidRequest(ruleId, acquireCount)) {
return badRequest();
}
// The rule should be valid.
//获取流控规则
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
//流控规则为空判断
if (rule == null) {
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
}
//获取流控结果
return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);
}
ClusterFlowChecker#acquireClusterToken 方法源码解析
ClusterFlowChecker#acquireClusterToken 是集群方式下的流控处理方法,主要做了一下几件事:
- 全局流控校验,如果全局流控检验不通过,则返回请求过多。
- 对指标为空进行判断。
- 从指标中获取每秒通过的评价请求数量及全局阀值,计算出剩余可以通过的请求数量,也就是通常所说的剩余的令牌。
- 计算出剩余的令牌如果大于等于 0,设置相关参数,返回流控通过。
- 计算出剩余的令牌如果小于 0,判断是否可以通过通过排队等待获取令牌,如果可以则封装结果返回,否则封装流控阻塞结果返回。
//com.alibaba.csp.sentinel.cluster.flow.ClusterFlowChecker#acquireClusterToken
static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) {
//获取id
Long id = rule.getClusterConfig().getFlowId();
//全局流控校验
if (!allowProceed(id)) {
//达到 QPS 限制
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
}
//根据 flowId 获取集群指标
ClusterMetric metric = ClusterMetricStatistics.getMetric(id);
//指标为空判断
if (metric == null) {
return new TokenResult(TokenResultStatus.FAIL);
}
//获取则正常通过的平均 QPS
double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
//QPS 全局阀值
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount();
//剩余的 QPS
double nextRemaining = globalThreshold - latestQps - acquireCount;
if (nextRemaining >= 0) {
//剩余的 QPS 大于等于0 是可以通过的
// TODO: checking logic and metric operation should be separated.
//指标信息记录
metric.add(ClusterFlowEvent.PASS, acquireCount);
metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
if (prioritized) {
//优先级设置
// Add prioritized pass.
metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);
}
// Remaining count is cut down to a smaller integer.
//封装结果返回
return new TokenResult(TokenResultStatus.OK)
.setRemaining((int) nextRemaining)
.setWaitInMs(0);
} else {
//剩余的 QPS 小于0
if (prioritized) {
//是优先的
// Try to occupy incoming buckets.
//获取等待的平均 QPS
double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);
//DEFAULT_MAX_OCCUPY_RATIO = 1.0d;
if (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) {
//occupyAvg 小于 最大占有率* 阀值 则去尝试占有下一个
int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);
// waitInMs > 0 indicates pre-occupy incoming buckets successfully.
if (waitInMs > 0) {
//表示占有成功 也就是流控通过
ClusterServerStatLogUtil.log("flow|waiting|" + id);
//组装结果返回 这里的结果是有等待时间的
return new TokenResult(TokenResultStatus.SHOULD_WAIT)
.setRemaining(0)
.setWaitInMs(waitInMs);
}
// Or else occupy failed, should be blocked.
}
}
// Blocked.
//被阻塞的 设置相关指标
metric.add(ClusterFlowEvent.BLOCK, acquireCount);
metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1);
ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount);
ClusterServerStatLogUtil.log("flow|block_request|" + id, 1);
if (prioritized) {
// Add prioritized block.
//设置优先的相关属性
metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount);
ClusterServerStatLogUtil.log("flow|occupied_block|" + id, 1);
}
//封装结果 这里的结果表请求应该被阻塞
return blockedResult();
}
}
FlowRuleChecker#applyTokenResult 方法源码解析
FlowRuleChecker#applyTokenResult 方法主要是对结果进行判断,最终会有四种结果,分别是通过规则、等待、回退到本地模式、流控规则不通过。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#applyTokenResult
private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context,
DefaultNode node,
int acquireCount, boolean prioritized) {
switch (result.getStatus()) {
case TokenResultStatus.OK:
//结果是 OK
return true;
case TokenResultStatus.SHOULD_WAIT:
// Wait for next tick.
//等待
try {
Thread.sleep(result.getWaitInMs());
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
case TokenResultStatus.NO_RULE_EXISTS:
case TokenResultStatus.BAD_REQUEST:
case TokenResultStatus.FAIL:
case TokenResultStatus.TOO_MANY_REQUEST:
//本地模式 或者 直接通过
return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
case TokenResultStatus.BLOCKED:
default:
//返回流控不通过
return false;
}
}
FlowRuleChecker#fallbackToLocalOrPass 方法源码解析
FlowRuleChecker#fallbackToLocalOrPass 方法判断是否要回退到本地模式,默认是回退到本地模式的,因为 fallbackToLocalWhenFail 默认是 true。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#fallbackToLocalOrPass
private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
//是否是回退到本地模式 默认是
//private boolean fallbackToLocalWhenFail = true;
if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {
//回退到本地
return passLocalCheck(rule, context, node, acquireCount, prioritized);
} else {
// The rule won't be activated, just pass.
//该规则直接工通过
return true;
}
}
FlowRuleChecker#passLocalCheck 方法源码解析
FlowRuleChecker#passLocalCheck 方法是非集群模式下的规则校验方法,首先会获取 Node,然后回对 Node 为空判断,Node 如果不为空,则获取对应的流量控制器执行 canPass 方法,这里一共有四种流量控制器,都是 TrafficShapingController 的实现类,如下:
- DefaultController:默认策略,直接拒绝。
- WarmUpController:预热启动。
- RateLimiterController:匀速排队等待。
- WarmUpRateLimiterController:预热匀速排队等待。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#passLocalCheck
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
//获取 selectedNode
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
//node 为空 返回 true 则通过
return true;
}
//获取流量控制器 rule.getRater()
return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
FlowRuleChecker#selectNodeByRequesterAndStrategy 方法源码解析
FlowRuleChecker#selectNodeByRequesterAndStrategy 方法会根据对应的流控模式,选取对应的 Node 节点统计,共有如下几种流控模式:
- 直接流控模式(DIRECT):最简单的流控模式,当前资源满足规则条件后直接限流。
- 关联流控模式(RELATE):定义一个关联接口,当关联接口满足规则条件后当前资源会限流。
- 链路流控模式(CHAIN):针对来源进行区分,定义一个入口资源,如果当前资源满足规则条件后只会对该入口进行限流。
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#selectNodeByRequesterAndStrategy
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
// The limit app should not be empty.
//获取限流资源名称
String limitApp = rule.getLimitApp();
//获取限流策略
int strategy = rule.getStrategy();
//获取资源入口 origin
String origin = context.getOrigin();
if (limitApp.equals(origin) && filterOrigin(origin)) {
//资源是入口 且 不能是“default”或“other”
if (strategy == RuleConstant.STRATEGY_DIRECT) {
//流控模式是直接模式 则从 context 中获取源调用方所代表的 Node
// Matches limit origin, return origin statistic node.
return context.getOriginNode();
}
//选择参考节点
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
//资源名称是 default
if (strategy == RuleConstant.STRATEGY_DIRECT) {
//流控模式是直接模式
// Return the cluster node.
//返回集群节点
return node.getClusterNode();
}
//选择参考节点
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
//资源名称是 other 且是其他来源
if (strategy == RuleConstant.STRATEGY_DIRECT) {
//流控模式是直接模式 返回 OriginNode
return context.getOriginNode();
}
//选择参考节点
return selectReferenceNode(rule, context, node);
}
return null;
}
//com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker.selectReferenceNode
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
//获取规则中的参考资源
String refResource = rule.getRefResource();
//获取规则策略模式
int strategy = rule.getStrategy();
if (StringUtil.isEmpty(refResource)) {
//参考资源为空 返回 null
return null;
}
if (strategy == RuleConstant.STRATEGY_RELATE) {
//流控模式为关联 则从集群环境中获取对应关联资源所代表的 Node
return ClusterBuilderSlot.getClusterNode(refResource);
}
if (strategy == RuleConstant.STRATEGY_CHAIN) {
//则判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的 Node,否则返回 null
if (!refResource.equals(context.getName())) {
//资源名称相等 返回 null null 表示通过
return null;
}
return node;
}
// No node.
return null;
}
DefaultController#canPass 方法源码解析
DefaultController#canPass 是默认的流量控制器,如果不能通过流控规则,则直接拒绝,主要逻辑如下:
- 从 Node 中获取已经使用过令牌数(这里会有线程数和 QPS 两种计数)。
- 判断已经使用的令牌数量加上当前请求的令牌数量是否会大于令牌上限,如果不大于令牌上限,则返回限流通过。
- 如果大于了令牌上限,有优先级且是 QPS 限流,会计算等待时间去尝试占用下一个时间窗口,如果占用成功,则让当前请求 sleep 到指定时间,否则返回流控不通过。
//com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
//获取当前记录的数据 也就是已经使用的令牌数量 这里会有 线程数 和 QPS 数两种模式
int curCount = avgUsedTokens(node);
if (curCount + acquireCount > count) {
//达到了限流的限制
//存在优先级 并且是基于 QPS 的限流
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);
//添加需要的 QPS
node.addOccupiedPass(acquireCount);
//睡眠到未来等待的时间窗口
sleep(waitInMs);
// PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
throw new PriorityWaitException(waitInMs);
}
}
return false;
}
//小于等于 count 没有达到限流
return true;
}
WarmUpController#canPass 方法源码解析
WarmUpController#canPass 方法是流控预热模式的实现方式,其使用的是令牌桶算法,具体逻辑如下:
- 首先获取当前时间窗口和上一个时间窗口的 QPS。
- syncToken 会更新剩余的令牌数 storedTokens 与最后填满令牌时间 lastFilledTime 的值。
- 获取剩余令牌数量判断其是否超过了警戒线值,如果超过了警戒线值,就要进行预热,在预热阶段允许通过的速率会比限流规则设定的速率要低,如果当前申请的令牌数量小于当前预热阶段的速率,则规则通过,否则规则不通过。
- 剩余令牌数量乜有超过警戒线值,当前时间窗口使用的令牌数和当前需要请求的令牌数小于规则的阀值,则规则通过,否则规则不通过。
//com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
//从当前节点中获取当前时间窗口的 QPS 也就是已经通过的 QPS
long passQps = (long) node.passQps();
//上一次时间窗口的 QPS
long previousQps = (long) node.previousPassQps();
//生产令牌的过程
syncToken(previousQps);
// 开始计算它的斜率
// 如果进入了警戒线,开始调整他的qps
//剩余令牌数
long restToken = storedTokens.get();
//判断剩余令牌数 和 警戒线的关系
if (restToken >= warningToken) {
//剩余令牌数 大于 警戒线 此时需要预热
//剩余令牌数-警戒线令牌数
long aboveToken = restToken - warningToken;
// 消耗的速度要比warning快,但是要比慢
// current interval = restToken*slope+1/count
//slope 斜率 其实就是生成令牌的速率
//计算预热时的一秒钟能够生成的 token 预热时候能过通过的 qps 会比规则的设置的速率低
double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
if (passQps + acquireCount <= warningQps) {
//当前时间窗口的 QPS + 要获取的令牌数 小于预热允许通过的请求数量 规则通过
return true;
}
} else {
//剩余令牌数 小于 警戒线
if (passQps + acquireCount <= count) {
//当前时间窗口的 QPS + 要获取的令牌数 小于 生产的 令牌数 count 规则通过 此时无需预热
return true;
}
}
return false;
}
WarmUpController#syncToken 方法源码解析
WarmUpController#syncToken 方法主要是发放令牌的,主要逻辑如下:
- 获取当前时间,去掉小于 1 秒的时间,因为令牌是按秒发放的。
- 如果当前时间小于上次发放令牌的时间则直接返回,不进行发放令牌的操作。
- 生成令牌(后面方法讲)。
- 更新剩余令牌数量和最后一次发放令牌的时间。
//com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController#syncToken
protected void syncToken(long passQps) {
//获取当前时间
long currentTime = TimeUtil.currentTimeMillis();
//当前时间去掉小于 1 秒的数字
currentTime = currentTime - currentTime % 1000;
//获取上次的时间
long oldLastFillTime = lastFilledTime.get();
if (currentTime <= oldLastFillTime) {
//当前时间小于上次的时间直接返回
return;
}
//获取旧的令牌数
long oldValue = storedTokens.get();
//生成新的令牌数量 系统预热
long newValue = coolDownTokens(currentTime, passQps);
//设置新值
if (storedTokens.compareAndSet(oldValue, newValue)) {
//获取当前的令牌数量
long currentValue = storedTokens.addAndGet(0 - passQps);
if (currentValue < 0) {
//如果令牌数量小于 0 设置为 0
storedTokens.set(0L);
}
//设置最后一次时间
lastFilledTime.set(currentTime);
}
}
WarmUpController#coolDownTokens 方法源码解析
WarmUpController#coolDownTokens 方法主要是生成令牌,主要逻辑如下:
- 获取旧的令牌数,赋值给新的令牌数。
- 判断旧的令牌数量是否小于警戒线,如果小于警戒线则按照正常速率发放令牌。
- 如果旧的令牌数量大于等于警戒线,表示此时系统处于冷却状态,需要预热,则按系统冷却状态的速率来发放令牌。
- 返回令牌。
//com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController#coolDownTokens
private long coolDownTokens(long currentTime, long passQps) {
//旧的令牌数
long oldValue = storedTokens.get();
//旧的令牌数 赋值给新的令牌数
long newValue = oldValue;
// 添加令牌的判断前提条件:
// 当令牌的消耗程度远远低于警戒线的时候
if (oldValue < warningToken) {
//旧的令牌数小于警戒线令牌数
//新的令牌数= 旧的令牌数+当前时间和上一次时间差之间消耗的令牌数
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
} else if (oldValue > warningToken) {
//旧的令牌数大于警戒线令牌数
if (passQps < (int)count / coldFactor) {
//上一秒的 QPS 小于预热最小的 QPS 也就是令牌消耗能力低于预热生成的最小能力时 即系统处于冷却过程
//也按正常速度生成新的令牌数 相当于降低了 QPS 这里将 QPS 降下来 以最快的速度降低 QPS
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
}
}
//返回 newValue 和 maxToken 中较小的一个值
return Math.min(newValue, maxToken);
}
RateLimiterController#canPass 方法源码解析
RateLimiterController#canPass 方法是排队等待的流控模式的实现,使用的是漏桶算法,也就是流出的速率是固定的,请求过来桶满了则溢出也就是不通过,具体逻辑如下:
- 对请求的令牌和令牌总数进行判断,如果小于 0 则直接返回。
- 计算当前请求的令牌需要多久时间生成,也就是当前请求通过需要需要的花费的时间。
- 计算当前请求请求通过的时间是否小于等于当前时间,如果是则更新最后一次请求通过的时间,并返回规则通过。
- 如果当前请求通过的时间大于当前时间,计算当前请求通过需要等待的时间,如果等待的时间大于最大等待时间,则返回规则不通过。
- 如果等待时间小于最大等待时间,设置最后请求最后通过时间,并让当前请求 sleep waitTime 时间,最后返回限流通过。
//com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// Pass when acquire count is less or equal than 0.
if (acquireCount <= 0) {
//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) {
//count 小于等于0直接拒绝
return false;
}
//获取当前时间
long currentTime = TimeUtil.currentTimeMillis();
// Calculate the interval between every two requests.
//计算当前请求的令牌需要多长时间生成
long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
// Expected pass time of this request.
//请求的预计通过时间
long expectedTime = costTime + latestPassedTime.get();
if (expectedTime <= currentTime) {
//预计通过时间 小于等于 当前时间 表示可以通过 设置最后一次通过时间
// Contention may exist here, but it's okay.
latestPassedTime.set(currentTime);
//规则通过
return true;
} else {
// Calculate the time to wait.
//计算等待时间
long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
if (waitTime > maxQueueingTimeMs) {
//等待时间大于最大排队时间 限流不通过
return false;
} else {
//等待时间不大于最大排队时间
//设置 latestPassedTime 时间 latestPassedTime=latestPassedTime+costTime
long oldTime = latestPassedTime.addAndGet(costTime);
try {
//等待时间
waitTime = oldTime - TimeUtil.currentTimeMillis();
if (waitTime > maxQueueingTimeMs) {
//等待时间大于最大排队时间 限流不通过 把 latestPassedTime 减回去
latestPassedTime.addAndGet(-costTime);
//限流不通过
return false;
}
// in race condition waitTime may <= 0
//多线程竞争的条件下 waitTime 可能小于 0
if (waitTime > 0) {
//睡眠等待时长
Thread.sleep(waitTime);
}
//限流通过
return true;
} catch (InterruptedException e) {
}
}
}
//限流不通过
return false;
}
WarmUpRateLimiterController#canPass 方法源码解析
WarmUpRateLimiterController 这个类继承自 WarmUpController,它实现的流控效果是排队等待,他本质上就是 RateLimiterController 和 WarmUpController 的结合体,WarmUpRateLimiterController#canPass 方法的主要逻辑如下:
- 获取上一个时间窗口的 QPS。
- 生产令牌(同 WarmUpController 的逻辑)。
- 获取当前剩余的令牌数,判断剩余令牌数量和警戒线的大小,计算出当前请求通过需要消耗的时间,从而得出当前请求的预计通过时间。
- 如果当前请求的预计通过时间小于等于当前时间,则规则通过。
- 如果当前请求的预计通过时间大于当前时间,则计算等待时间,如果等待时间超过超时时间,则返回规则不通过,否则让当前请求 sleep waitTime 时间,然后返回规则通过。
//com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
//上一个时间窗口通过的 QPS
long previousQps = (long) node.previousPassQps();
//生产令牌
syncToken(previousQps);
//获取当前时间
long currentTime = TimeUtil.currentTimeMillis();
//剩余的令牌数
long restToken = storedTokens.get();
//时间成本
long costTime = 0;
//预计时间
long expectedTime = 0;
//剩余的令牌是否大于警戒线
if (restToken >= warningToken) {
//大于等于警戒线
//剩余的令牌-警戒线令牌
long aboveToken = restToken - warningToken;
// current interval = restToken*slope+1/count
//计算预热时的 QPS
double warmingQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
//计算产生请求的令牌需要的时间
costTime = Math.round(1.0 * (acquireCount) / warmingQps * 1000);
} else {
//小于警戒线
//计算产生请求的令牌需要的时间
costTime = Math.round(1.0 * (acquireCount) / count * 1000);
}
//预计时间
expectedTime = costTime + latestPassedTime.get();
if (expectedTime <= currentTime) {
//预计时间小于等于 当前时间 设置最后一次的时间
latestPassedTime.set(currentTime);
//返回 true 规则通过
return true;
} else {
//预计时间大于当前时间
//计算需要等待的时间
long waitTime = costTime + latestPassedTime.get() - currentTime;
if (waitTime > timeoutInMs) {
//等待的时间大于超时时间 直接返回 false
return false;
} else {
//等待的时间小于超时时间
//设置最后一次时间 latestPassedTime=latestPassedTime+costTime
long oldTime = latestPassedTime.addAndGet(costTime);
try {
//计算等待时间
waitTime = oldTime - TimeUtil.currentTimeMillis();
if (waitTime > timeoutInMs) {
//等待时间大于超时时间 回复刚刚加的时间 返回false 规则不通过
latestPassedTime.addAndGet(-costTime);
return false;
}
if (waitTime > 0) {
//等待 waitTime 时间
Thread.sleep(waitTime);
}
//返回 true 规则放行
return true;
} catch (InterruptedException e) {
}
}
}
return false;
}
如有不正确的地方请各位指出纠正。