Sentinel源码(一)基础概念

1,586 阅读7分钟

前言

本章初步了解一下Sentinel中的一些基础概念,主要包括:

  1. Resource:资源
  2. Context:上下文
  3. Sph:信号量
  4. Entry:Token/许可/入口---调用链
  5. Node:统计节点---树
  6. ProcessorSlot:插槽
  7. Rule:规则

注:基于Sentinel 1.8版本。

1、Resource

Resource是Sentinel对资源的一种抽象。

public abstract class ResourceWrapper {
    // 资源名称
    protected final String name;
    // IN or OUT 是入口还是出口流量
    protected final EntryType entryType;
    // 资源类型
    protected final int resourceType;  
}

其中EntryType流量类型,分为入口流量和出口流量。

public enum EntryType {
    IN("IN"),
    OUT("OUT");
}

其中resourceType资源类型,目前分为四种。

public final class ResourceTypeConstants {
    // 默认普通资源
    public static final int COMMON = 0;
    // http
    public static final int COMMON_WEB = 1;
    // RPC:Dubbo、Motan、Sofa-RPC
    public static final int COMMON_RPC = 2;
    // spring-cloud-gateway、zuul
    public static final int COMMON_API_GATEWAY = 3;
}

Resource有两个实现类。

一个是StringResourceWrapper,用字符串表示一个资源。

public class StringResourceWrapper extends ResourceWrapper {
    public StringResourceWrapper(String name, EntryType e) {
        super(name, e, ResourceTypeConstants.COMMON);
    }
    public StringResourceWrapper(String name, EntryType e, int resType) {
        super(name, e, resType);
    }
}

另一类是MethodResourceWrapper,用方法表示一个资源,其底层仍然是解析Method的方法签名为字符串,用字符串作为资源标识。

public class MethodResourceWrapper extends ResourceWrapper {
    private final transient Method method;
    public MethodResourceWrapper(Method method, EntryType e) {
        this(method, e, ResourceTypeConstants.COMMON);
    }
    public MethodResourceWrapper(Method method, EntryType e, int resType) {
        super(MethodUtil.resolveMethodName(method), e, resType);
        this.method = method;
    }
}

2、Context

Context上下文,通过 ThreadLocal 传递,即每个线程持有一个Context。

public class Context {
    // 上下文名称
    private final String name;
    // EntranceNode 入口节点 --- 树
    private DefaultNode entranceNode;
    // 当前Entry --- 链表
    private Entry curEntry;
    // 来源
    private String origin = "";
}

Context中保存了两个重要的属性:

  • entranceNode:EntranceNode入口节点,当创建Context时创建,可以统计整个Context的统计指标
  • curEntry:当前上下文中,用户最后一个获得到的Entry

3、Sph

用户调用SphU(或SphO) #entry方法,获取资源许可。

如果抛出异常,代表获取失败,如果成功返回Entry实例代表获取资源成功。

public class SphU {
    public static Entry entry(String name) throws BlockException {
        return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
    }
}

Sph信号量,是用户获取资源的入口。无论是SphO.entry还是SphU.entry,其底层都是通过Sph的实现类CtSph获取信号量。

public interface Sph extends SphResourceTypeSupport {
    Entry entry(String name) throws BlockException;
    Entry entry(Method method) throws BlockException;
    // ... 省略其他方法
}

Sph保存在全局Env中。

public class Env {
    public static final Sph sph = new CtSph();
    static {
        InitExecutor.doInit();
    }
}

4、Entry

Entry,代表一个许可/入口,当Sph.entry获取资源成功时返回。

用户使用完资源后,需要通过Entry.exit归还许可,释放Sph信号量。

Entry抽象类主要包含:Node指标统计信息、Resource信息。

public abstract class Entry implements AutoCloseable {
    // 进入时间
    private final long createTimestamp;
    // 退出时间
    private long completeTimestamp;
    // 当前上下文的统计信息(包含子Node)
    private Node curNode;
    // 来源方统计信息
    private Node originNode;
    // 资源
    protected final ResourceWrapper resourceWrapper;
}

Entry的默认实现类是CtEntry

  1. parent和child:将多个Entry串联成了链表,当多次调用Sph.entry时会形成一个调用链,当调用Entry.exit时,会将自己从链表中移除
  2. chain:ProcessorSlot,实际上是ProcessorSlotChain,代表了当前Entry需要经过的规则检查(如FlowRule限流规则检查)
  3. Context:上下文
class CtEntry extends Entry {
    // 上一个入口Entry
    protected Entry parent = null;
    // 下一个Entry
    protected Entry child = null;
    // Slot插槽
    protected ProcessorSlot chain;
    // 上下文
    protected Context context;
}

5、Node

Node负责指标统计,包含QPS、Thread、RT等指标。

public interface Node extends OccupySupport, DebugSupport {
    long totalRequest();
    long totalPass();
    long totalSuccess();
    long blockRequest();
    long totalException();
    double passQps();
    double blockQps();
    double totalQps();
    double successQps();
    double maxSuccessQps();
    double exceptionQps();
    double avgRt();
    double minRt();
    int curThreadNum();
    double previousBlockQps();
    double previousPassQps();
    Map<Long, MetricNode> metrics();
    List<MetricNode> rawMetricsInMin(Predicate<Long> timePredicate);
    void addPassRequest(int count);
    void addRtAndSuccess(long rt, int success);
    void increaseBlockQps(int count);
    void increaseExceptionQps(int count);
    void increaseThreadNum();
    void decreaseThreadNum();
    void reset();
}

Node的实现类有四个

Node.png

StatisticNode

StatisticNode主要负责做指标统计,所有的Node都继承了StatisticNode。

public class StatisticNode implements Node {
    // 秒级滑动窗口,SAMPLE_COUNT=2,代表每500ms一个时间窗口
    private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
        IntervalProperty.INTERVAL);
    // 分钟级滑动窗口,SAMPLE_COUNT=60,代表每1s一个时间窗口
    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
    // 当前并发线程数量
    private LongAdder curThreadNum = new LongAdder();
}

StatisticNode中包含一个ArrayMetric秒级滑动窗口和一个ArrayMetric分钟级滑动窗口,用于保存统计数据,但两者的用途不同。

比如统计被Sentinel拒绝的QPS,从秒级滑动窗口获取:

@Override
public double blockQps() {
    return rollingCounterInSecond.block() / rollingCounterInSecond.getWindowIntervalInSec();
}

比如统计被Sentinel拒绝的请求总数,从分钟级滑动窗口获取:

@Override
public long blockRequest() {
    return rollingCounterInMinute.block();
}

此外有个LongAddr负责统计并发线程数量:

@Override
public int curThreadNum() {
    return (int)curThreadNum.sum();
}
@Override
public void increaseThreadNum() {
  curThreadNum.increment();
}

@Override
public void decreaseThreadNum() {
  curThreadNum.decrement();
}

DefaultNode

DefaultNode,普通链路节点。

统计维度:Context + Resource。

创建时机:多次调用SphU.entry,NodeSelectorSlot执行时创建DefaultNode(如果Context+Resource维度已经有这个Node了,将使用原来的Node实例),并加入Context上下文的Node链表中。

public class DefaultNode extends StatisticNode {
    // 关联的资源
    private ResourceWrapper id;
    // 子节点
    private volatile Set<Node> childList = new HashSet<>();
    // 关联的ClusterNode
    private ClusterNode clusterNode;
}

DefaultNode维护了一个Node集合,用于表示当前节点的子节点,通过这种方式,DefaultNode构成了一颗树

DefaultNode重写了StaticNode更新统计数据的相关方法,如increaseBlockQps增加被拒绝的QPS时,额外调用了ClusterNode的increaseBlockQps方法。

@Override
public void increaseBlockQps(int count) {
    super.increaseBlockQps(count);
    this.clusterNode.increaseBlockQps(count);
}

EntranceNode

EntranceNode入口节点,是特殊的DefaultNode链路节点。

统计维度:Context。

创建时机:每个上下文Context,都会有一个EntranceNode与之关联,代表链路的入口。

EntranceNode继承了DefaultNode,是Node树的树根

EntranceNode重写了所有统计方法,比如avgRt统计平均响应时间,根据所有子节点的统计数据,计算得到最终的平均响应时间。

public class EntranceNode extends DefaultNode {
    public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) {
        super(id, clusterNode);
    }
    @Override
    public double avgRt() {
        double total = 0;
        double totalQps = 0;
        for (Node node : getChildList()) {
            total += node.avgRt() * node.passQps();
            totalQps += node.passQps();
        }
        return total / (totalQps == 0 ? 1 : totalQps);
    }
}

ClusterNode

ClusterNode簇点,统计一个Resource的数据。

统计维度:Resource。

创建时机:多次调用Sph.entry获取不同资源,ClusterBuilderSlot执行时创建ClusterNode。如果这个Resource从来没有被entry调用过,则创建一个ClusterNode,否则沿用Resource对应的ClusterNode。

public class ClusterNode extends StatisticNode {
    // 资源名称
    private final String name;
    // 资源类型
    private final int resourceType;
    // origin来源 - 统计数据
    private Map<String, StatisticNode> originCountMap = new HashMap<>();
}

一个Resource对应一个ClusterNode,ClusterNode还根据Sph.entry时传入的origin不同(origin存储在上下文中),在originCountMap中保存了Resource下不同origin的统计数据StatisticNode。

6、ProcessorSlot

在执行Sph.entry和Entry.exit时,会经过一系列插槽(ProcessorSlot),每个插槽负责一部分功能(责任链)。

public interface ProcessorSlot<T> {
    // Sph.entry时触发
    void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
               Object... args) throws Throwable;
    // 执行下一个Slot的entry方法
    void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
                   Object... args) throws Throwable;
    // Entry.exit时触发
    void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
    // 执行下一个Slot的exit方法
    void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}

slots.gif

目前总共有7个重要的Slot,按照Slot排列顺序如下(以文字为准,官方图片与代码顺序不一致):

  1. NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储,即图中Tree Node Builder。
  2. ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息,即图中Cluster Node Builder。
  3. StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑。
  4. AuthoritySlot:授权规则校验
  5. SystemSlot:系统规则校验
  6. ParamFlowSlot:热点参数流控规则校验
  7. FlowSlot:流控规则校验
  8. DegradeSlot:降级规则校验

7、Rule

Rule,规则,定义了对于某个Resource的限制。

public interface Rule {
    // 资源名称 - ResourceWrapper.name
    String getResource();
}

AbstractRule,抽象规则。

除了提供Resource的getter&setter外,定义了limitApp字段,用于标识规则适用的来源系统。

目前limitApp分为default、other和用户指定,默认情况下为default。

public abstract class AbstractRule implements Rule {
    // 资源名称 - ResourceWrapper.name
    private String resource;
    // 来源应用
    private String limitApp;

    @Override
    public String getResource() {
        return resource;
    }
    // ...
}

Sentinel支持5个规则定义分别对应5个规则校验ProcessorSlot:

  • AuthorityRule:授权规则
  • SystemRule:系统规则
  • ParamFlowRule:热点参数流控规则
  • FlowRule:流控规则
  • DegradeRule:降级规则

总结

本章简单了解了Sentinel的7个概念

  • Resource:资源,根据EntryType流量类型,分为入口流量和出口流量。

  • Context:上下文,通过 ThreadLocal 传递,即每个线程持有一个Context。持有Node树的根节点EntranceNode,持有当前上下文中用户最后一个获得到的Entry。

  • Sph:信号量,是用户获取资源许可的入口。无论是SphO.entry还是SphU.entry,其底层都是通过Sph的实现类CtSph获取信号量。

  • Entry:Token/许可/入口,当Sph.entry获取资源成功时返回。实现类CtEntry用指针构成双向链表,形成一个调用链。

  • Node:统计节点,负责指标统计,包含QPS、Thread、RT等指标。共有四个实现类:

    • StatisticNode:负责指标统计,所有的Node都继承了StatisticNode;
    • DefaultNode:普通链路节点,统计Context * Resource维度的数据。用一个集合存储了当前Node的所有子Node,形成一棵树;
    • EntranceNode:入口节点,继承了DefaultNode,是特殊的链路节点,统计Context维度的数据。每个Context都对应一个EntranceNode。
    • ClusterNode:簇点,统计Resource维度的数据。
  • ProcessorSlot:插槽,Sph.entry和Entry.exit时,会经过一系列插槽(ProcessorSlot),每个插槽负责一部分功能(责任链)。

    • NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode)
    • ClusterBuilderSlot:构建ClusterNode
    • StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑
    • AuthoritySlot:授权规则校验
    • SystemSlot:系统规则校验
    • ParamFlowSlot:热点参数流控规则校验
    • FlowSlot:流控规则校验
    • DegradeSlot:降级规则校验
  • Rule:规则,Sentinel支持5个常用规则,分别对应5个规则校验ProcessorSlot

    • AuthorityRule:授权规则
    • SystemRule:系统规则
    • ParamFlowRule:热点参数流控规则
    • FlowRule:流控规则
    • DegradeRule:降级规则