前言:
上一篇我对 Sentinel 中统计数据部分的 NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot 的相关源码进行了分析,本篇我们开始分析规则相关的源码。
Sentinel 系列文章传送门:
Sentinel 初步认识及使用
Sentinel 核心概念和工作流程详解
Spring Cloud 整合 Nacos、Sentinel、OpenFigen 实战【微服务熔断降级实战】
Sentinel 源码分析入门【Entry、Chain、Context】
Sentine 源码分析之–NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot
AuthoritySlot
关于 AuthoritySlot 官方是如下描述:
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
AuthoritySlot 授权规则配置如下:
代码中的规则详情如下:
AuthoritySlot#entry 方法源码解析
AuthoritySlot#entry 方法还是老套路,执行一个校验方法,然后执行下一个 Sllot,重点关注校验方法。
//com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
throws Throwable {
//检查黑白名单权限
checkBlackWhiteAuthority(resourceWrapper, context);
//进入下一个 slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
AuthoritySlot#checkBlackWhiteAuthority 方法源码解析
AuthoritySlot#checkBlackWhiteAuthority 方法先是对规则和当前资源的规则进行为空判断,如果为空直接返回,否则循环遍历所有规则执行校验动作。
//com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#checkBlackWhiteAuthority
void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
//获取所有权限规则
Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();
//authorityRules 为空判断
if (authorityRules == null) {
return;
}
//根据当前资源的 name 获取规则
Set<AuthorityRule> rules = authorityRules.get(resource.getName());
if (rules == null) {
//规则为空 返回
return;
}
//遍历所有规则
for (AuthorityRule rule : rules) {
//校验规则
if (!AuthorityRuleChecker.passCheck(rule, context)) {
//规则不通过直接抛出异常
throw new AuthorityException(context.getOrigin(), rule);
}
}
}
ConfigCacheService#dumpBeta 方法源码解析
AuthorityRuleChecker#passCheck 方法主要做了一下几件事:
- 对请求来源 orgin 和 黑白名单为空判断,如果为空则直接返回true,规则通过。
- 判断请求来源是否在黑白名单中,如果在还会进行完全匹配,如果完全匹配也满足,就认为当前请求来源在黑白名单中。
- 对规则策略进行判断,是黑名单类型且完全匹配到则返回规则不通过,是白名单类型且没有完全匹配到返回规则不通过,其他情况返回规则通过。
规则配置项解释如下:
- resource:资源名,限流规则的作用对象。
- limitApp:对应的黑白名单,不同 origin 用英文逗号分隔,如 app1,app2,app3。
- strategy:规则策略,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。
//com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleChecker#passCheck
static boolean passCheck(AuthorityRule rule, Context context) {
//获取请求来源 orgin
String requester = context.getOrigin();
// Empty origin or empty limitApp will pass.
//请求来源 orgin 为空 或者 对应的黑白名单为空 都直接返回
if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
return true;
}
// Do exact match with origin name.
//getLimitApp 就是黑白名单 indexOf 判断 请求来源是否在 黑白名单中
int pos = rule.getLimitApp().indexOf(requester);
boolean contain = pos > -1;
if (contain) {
//在黑白名单中 还需要进一步判断
boolean exactlyMatch = false;
//黑白名单数组
String[] appArray = rule.getLimitApp().split(",");
for (String app : appArray) {
//进行 equals 完全匹配
if (requester.equals(app)) {
//完全匹配到了
exactlyMatch = true;
//终止循环
break;
}
}
//完全匹配的值 赋值给 contain
contain = exactlyMatch;
}
//获取规则策略 也就是 到底是黑明单 还是白名单
int strategy = rule.getStrategy();
if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
//是黑名单 且完全匹配到了 返回 false
return false;
}
if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
//是白名单 且没有完全匹配到 返回 false
return false;
}
return true;
}
SystemSlot
SystemSlot 是系统级限流器。
SystemSlot 系统规则配置如下:
SystemSlot#entry 方法源码解析
SystemSlot#entry 方法同样是老套路,进行规则校验,执行下一个 Slot,我们重点关注规则校验。
//com.alibaba.csp.sentinel.slots.system.SystemSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
//系统规则校验
SystemRuleManager.checkSystem(resourceWrapper);
//进入下一个 slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
SystemRuleManager#checkSystem 方法源码解析
SystemRuleManager#checkSystem 方法主要做了如下几件事:
- 校验资源是否为空。
- 检查系统限流开关是否打开。
- 对资源类型进行校验,系统限流只校验入口流量。
- 对 QPS 、线程数和 maxRt 进行校验,如果大于系统阀值,则直接抛出异常。
- 对 CPU 使用率和系统负载进行校验,如果大于系统阀值,则直接抛出异常。
//com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
//资源包装器为空判断
if (resourceWrapper == null) {
return;
}
// Ensure the checking switch is on.
//检查开关是否打开 如果没有打开 直接返回
if (!checkSystemStatus.get()) {
return;
}
// for inbound traffic only
//只对入口资源进行校验 其他资源直接返回
if (resourceWrapper.getEntryType() != EntryType.IN) {
return;
}
// total qps
//获取 ClusterNode 记录的成功的 QPS
double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
//判断是否大于 Double.MAX_VALUE 大于则抛出异常
if (currentQps > qps) {
throw new SystemBlockException(resourceWrapper.getName(), "qps");
}
// total thread
//获取 ClusterNode 记录的 线程数
int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
//线程数大于 Long.MAX_VALUE 抛出异常
if (currentThread > maxThread) {
throw new SystemBlockException(resourceWrapper.getName(), "thread");
}
//获取 ClusterNode 记录的平均 rt
double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
//最大 rt 大于 Long.MAX_VALUE 抛出异常
if (rt > maxRt) {
throw new SystemBlockException(resourceWrapper.getName(), "rt");
}
// load. BBR algorithm.
//全局系统负载校验
//highestSystemLoadIsSet 默认 false
//getCurrentSystemAvgLoad() 默认返回 -1
//highestSystemLoad = Double.MAX_VALUE;
//默认情况下是进入不了 if 的
if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
if (!checkBbr(currentThread)) {
throw new SystemBlockException(resourceWrapper.getName(), "load");
}
}
// cpu usage
//全局 CPU 校验
//highestCpuUsageIsSet 默认 false
//getCurrentCpuUsage() 默认返回 -1
//highestCpuUsage = Double.MAX_VALUE;
//默认情况下是进入不了 if 的
if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
throw new SystemBlockException(resourceWrapper.getName(), "cpu");
}
}
SystemRuleManager#checkBbr 方法源码解析
SystemRuleManager#checkBbr 方法判断 ClusterNode 中记录的线程数大于系统最大负载则范湖规则不通过。
//com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr
private static boolean checkBbr(int currentThread) {
//currentThread ClusterNode 中记录的线程数
if (currentThread > 1 &&
currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) {
//ClusterNode 中记录的线程数 大于 请求成功的 QPS 乘以 最小 RT 除以 1000 得到的是系统最大负载
//如果 线程数已经大于最大负载了 直接返回 false
return false;
}
return true;
}
GatewayFlowSlot
Sentinel 支持 API 网关限流。
GatewayFlowSlot#entry 方法源码解析
GatewayFlowSlot#entry 方法只做了两件事,规则校验和进入下一个 Slot,我们重点关注规则校验。
//com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot#entry
@Override
public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
//检查网关参数 也就是规则校验
checkGatewayParamFlow(resource, count, args);
//进入下一个 slot
fireEntry(context, resource, node, count, prioritized, args);
}
GatewayFlowSlot#checkGatewayParamFlow 方法源码解析
GatewayFlowSlot#checkGatewayParamFlow 方法首先对参数进行校验,如果参数为空则直接返回无需进行规则校验,否则获取规则对规则为空进行判断,如果规则不为空则执行规则校验,具体的规则校验我们会在在下一篇 ParamFlowSlot 中详细分析。
//com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot#checkGatewayParamFlow
private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args)
throws BlockException {
//参数为空判断
if (args == null) {
return;
}
//获取参数规则
List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
//规则为空判断
if (rules == null || rules.isEmpty()) {
return;
}
//遍历规则
for (ParamFlowRule rule : rules) {
// Initialize the parameter metrics.
//初始化参数指标
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
//执行规则校验
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
//校验不通过 抛出异常
String triggeredParam = "";
if (args.length > rule.getParamIdx()) {
Object value = args[rule.getParamIdx()];
triggeredParam = String.valueOf(value);
}
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
}
}
}
如有不正确的地方请各位指出纠正。