《一起学sentinel》五、Slot的子类及实现之AuthoritySlot和SystemSlot

1,904 阅读6分钟

一、概述

在 Sentinel 里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个 Entry 对象。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 SphU API 显式创建。Entry 创建候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如:

  • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
  • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
  • LogSlot则用于记录用于记录块异常,为故障排除提供具体的日志
  • StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
  • FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
  • AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
  • DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
  • SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;

下面是关系结构图

ProcessorSlot子类及实现类.png


二、AuthoritySlot分析

1.AuthoritySlot介绍

官方文档是这样描述AuthoritySlot的:

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

调用方信息通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数传入。

规则配置

来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即限流规则的作用对象。
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。

示例

比如我们希望控制对资源 test 的访问设置白名单,只有来源为 appAappB 的请求才可通过,则可以配置如下白名单规则:

AuthorityRule rule = new AuthorityRule();
rule.setResource("test");
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
rule.setLimitApp("appA,appB");
AuthorityRuleManager.loadRules(Collections.singletonList(rule));

2.源码解读

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)throws Throwable {
    checkBlackWhiteAuthority(resourceWrapper, context);
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
    fireExit(context, resourceWrapper, count, args);
}

1.在entry阶段,执行了一个校验方法.

void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
        Map<String, Set<AuthorityRule>> authorityRules =  AuthorityRuleManager.getAuthorityRules();

        if (authorityRules == null) {
            return;
        }

        Set<AuthorityRule> rules = authorityRules.get(resource.getName());//根据resource获取指定rules
        if (rules == null) {
            return;
        }

        for (AuthorityRule rule : rules) {
            if (!AuthorityRuleChecker.passCheck(rule, context)) {
                throw new AuthorityException(context.getOrigin(), rule);
            }
        }
    }

2.如果AuthorityRuleManager里面的authorityRules为空,则跳过黑白名单的校验,否则根据resource获取本资源的rules(Set),循环这个rules,在循环里面调用校验方法(AuthorityRuleChecker.passCheck(rule, context)

static boolean passCheck(AuthorityRule rule, Context context) {
    String requester = context.getOrigin();

    // Empty origin or empty limitApp will pass.
    if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
        return true;
    }

    // Do exact match with origin name.
    int pos = rule.getLimitApp().indexOf(requester);//包含关系验证
    boolean contain = pos > -1;

    if (contain) {//equals验证
        boolean exactlyMatch = false;
        String[] appArray = rule.getLimitApp().split(",");
        for (String app : appArray) {
            if (requester.equals(app)) {
                exactlyMatch = true;
                break;
            }
        }

        contain = exactlyMatch;
    }

    int strategy = rule.getStrategy();
    if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {//黑名单验证
        return false;
    }

    if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {//白名单验证
        return false;
    }

    return true;
}

3.这个核心的校验方法内,首先会进行基础判断,Empty origin or empty limitApp will pass.。然后判断resource中的rule,是否已经注册到了rule中,如果注册了并且能够对应的上,exactlyMatch参数会被置为true

4.拿到了对应结果contain(exactlyMatch) 后,开始进行strategy 的判断。如果是黑名单,则返回contain;如果是白名单验证则返回!contain

作为sentinel 的黑白名单控制,实现的功能也比较简单,只需要我们需要定义控制规则就行了。

多个相同的resource(name),对应着不同的context(name),那么我们就可以快速统计出某个资源的总统计信息。对应着多个(归属于同一个resource的)DefaultNode对应同一个ClusterNode

三、SystemSlot分析

1.SystemSlot介绍

官方文档是这样描述SystemSlot的:一句话概括就是系统级限流的控制器

2.源码解读

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,  boolean prioritized, Object... args) throws Throwable {
    SystemRuleManager.checkSystem(resourceWrapper);//整个系统限流的切入点
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
    fireExit(context, resourceWrapper, count, args);
}

1.在下一个slot的开始前,直接调用了com.alibaba.csp.sentinel.slots.system.SystemRuleManager的checkSystem方法。

public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
    if (resourceWrapper == null) {
        return;
    }
    // 是否打开校验开关
    if (!checkSystemStatus.get()) {
        return;
    }

    // 是否为入口流量
    if (resourceWrapper.getEntryType() != EntryType.IN) {
        return;
    }

    //qps 的对比
    double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
    if (currentQps > qps) {
        throw new SystemBlockException(resourceWrapper.getName(), "qps");
    }

    //线程数的对比
    int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
    if (currentThread > maxThread) {
        throw new SystemBlockException(resourceWrapper.getName(), "thread");
    }

    //获得总成功计数的对比
    double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
    if (rt > maxRt) {
        throw new SystemBlockException(resourceWrapper.getName(), "rt");
    }

    // 按照BBR算法
    if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
        if (!checkBbr(currentThread)) {
            throw new SystemBlockException(resourceWrapper.getName(), "load");
        }
    }

    // cpu状态
    if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
        throw new SystemBlockException(resourceWrapper.getName(), "cpu");
    }
}

2.进入到校验方案,惯例 的校验了一次resource的非空。

3.开始校验是否打开系统限流开关,流量类型(全局限流只校验入口流量)。

4.进行qps,线程数的校验,当超过任何系统规则的阈值时throws BlockException。

5.进行成功访问数,RT,CPU状态的校验,当超过任何系统规则的阈值时throws BlockException。

四、小结

本期我们讲述了Slot的子类LogSlotStatisticSlot的基本实现原理。

现在建立我们的知识树

实例化DefaultNode和ClusterNode,创建结构树


创建上下文时,首先会在NodeSelectorSlot中判断是否有DefaultNode

如果没有则新增一个基于resourceDefaultNode,然后执行下一个slot

下一个slotClusterBuilderSlotClusterBuilderSlot会判断是否有对应的ClusterNode,如果没有则新增一个基于resource的ClusterNode并继续下一个流程(slot)。

总结来说,这个两个slot奠定了一个基于resource进行全局控制的基调。

进行信息收集


LogSlotDefaultNodeClusterNode初始化后,作为业务实例模块的分界点,收集全局异常并处理。

StatisticSlot作为全局统计的实例,依托于ClusterNode,将全局的RT, QPS, thread count 等等信息存放在clusterNodeMap里面。

进行权限校验及系统级限流


在树结构和信息收集的slot建立完毕后,开始业务逻辑的实现,首先实现的就是AuthoritySlot的黑白名单能力,依托sentinel的resource的定义,我们很简单就可以拿到关于resource的authorityRules,将对应的rules取出后,以此进行黑、白名单判断,也可以理解为一种权限级别的限流措施。

SystemSlot则是全统计的全局限流,从调用点origins级别的配置中读取了配置好的限流措施,在下一个slot实现前完成了所有的判断,如qps,线程数,成功访问数,RT,CPU状态。如果出现异常,则throws BlockException,交给之前的slot去处理相应逻辑。到这里,一个基础的限流框架已经基本实现。