Sentinel笔记-核心类

一些概念

  • 资源:资源是 Sentinel 的关键概念。资源,可以是一个方法、一段代码、由应用提供的接口,或者由应用调用其它应用的接口。
  • 规则:围绕资源的实时状态设定的规则,包括流量控制规则、熔断降级规则以及系统保护规则、自定义规则。
  • 降级:在流量剧增的情况下,为保证系统能够正常运行,根据资源的实时状态、访问流量以及系统负载有策略的拒绝掉一部分流量。

Resource 

Sentinel 中指标数据统计以资源为维度。资源使用 ResourceWrapper 对象表示,我们把 ResourceWrapper 对象称为资源 ID。如果一个资源描述的是一个接口,那么资源名称通常就是接口的 url,例如“GET:/v1/demo”。

ResourceWrapper 有三个字段:

  • name 为资源名称,例如:“GET:/v1/demo”。
  • entryType 为流量类型,即流入流量还是流出流量,通俗点说就是发起请求还是接收请求。
  • resourceType 表示资源的类型,例如 Dubbo RPC、Web MVC 或者 API Gateway 网关。
public abstract class ResourceWrapper {
    protected final String name;
    protected final EntryType entryType;
    protected final int resourceType;
    public ResourceWrapper(String name, EntryType entryType, int resourceType) {
        this.name = name;
        this.entryType = entryType;
        this.resourceType = resourceType;
    }
}

//entryType
public enum EntryType {
    IN,
    OUT;
}

//resourceType
public final class ResourceTypeConstants {
    public static final int COMMON = 0;
    public static final int COMMON_WEB = 1;
    public static final int COMMON_RPC = 2;
    public static final int COMMON_API_GATEWAY = 3;
    public static final int COMMON_DB_SQL = 4;
}

 

 

Node

Node 用于持有实时统计的指标数据,Node 接口定义了一个 Node 类所需要提供的各项指标数据统计的相关功能,内部由具体滑动窗口实现。

它的几个实现类:DefaultNode、ClusterNode、EntranceNode、StatisticNode 的关系如下图所示。

 

 

StatisticNode

Statistic 即统计的意思,StatisticNode 是 Node 接口的实现类,是实现实时指标数据统计 Node,包含秒级(可抢占OccupiableBucketLeapArray)和分钟级(BucketLeapArray)两个滑动窗口结构。

StatisticNode 还负责统计并行占用的线程数,用于实现信号量隔离,按资源所能并发占用的最大线程数实现限流。当接收到一个请求就将 curThreadNum 自增 1,当处理完请求时就将 curThreadNum 自减一,如果同时处理 10 个请求,那么 curThreadNum 的值就为 10。

//对外提供统一的metric操作接口
public interface Node {
    long totalRequest(); // 获取总的请求数
    long totalPass(); // 获取通过的请求总数
    long totalSuccess(); // 获取成功的请求总数
    *****
    void increaseThreadNum(); // 自增占用线程
    void decreaseThreadNum(); // 自减占用线程
    void reset(); // 重置滑动窗口
}

public class StatisticNode implements Node {
    // 秒级滑动窗口,2 个时间窗口大小为 500 毫秒的 Bucket
    private  Metric rollingCounterInSecond = new ArrayMetric(2,1000);

    // 分钟级滑动窗口,60 个 Bucket 数组,每个 Bucket 统计的时间窗口大小为 1 秒
    private  Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);

    // 统计并发使用的线程数
    private LongAdder curThreadNum = new LongAdder();

    //**具体操作由内部的ArrayMetric 滑动窗口来实现。
    @Override
    public void addPassRequest(int count) {
        rollingCounterInSecond.addPass(count);
        rollingCounterInMinute.addPass(count);
    }

    @Override
    public void addRtAndSuccess(long rt, int successCount) {
        rollingCounterInSecond.addSuccess(successCount);
        rollingCounterInSecond.addRT(rt);

        rollingCounterInMinute.addSuccess(successCount);
        rollingCounterInMinute.addRT(rt);
    }
    *************    
    @Override
    public void increaseThreadNum() {
        curThreadNum.increment();
    }

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

 

ClusterNode

Sentinel 使用 ClusterNode 统计每个资源全局的指标数据,以及统计该资源按调用来源区分的指标数据。全局数据指的是不区分调用链路,一个资源 ID(Resource) 只对应一个 ClusterNode。

public class ClusterNode extends StatisticNode {
    // 资源名称 
      private final String name;

    // 资源类型
    private final int resourceType;

    // 来源指标数据统计,记录不同来源App的统计信息。
      private Map<String, StatisticNode> originCountMap = new HashMap<>();

} 

DefaultNode 

DefaultNode 是实现以资源为维度的指标数据统计的 Node,是将资源 ID 和 StatisticNode 映射到一起的 Node,用于统计调用链路上某个资源的数据,维持树状结构。

DefaultNode 是 StatisticNode 的子类,构造方法要求传入资源 ID,表示该 Node 用于统计哪个资源的实时指标数据,指标数据统计则由父类 StatisticNode 完成。

public class DefaultNode extends StatisticNode {

    private ResourceWrapper id;

    private volatile Set<Node> childList = new HashSet<>();
  
    //每个resourceId 对应一个clusterNode  
    private ClusterNode clusterNode;
}

 

childList 是一个 Node(DefaultNode)集合,用于存放子节点.

备注:在使用同一个 Context 的情况下,只会为一个资源创建一个 DefaultNode 

 

EntranceNode

  1. 入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。
  2. EntranceNode 是一个特殊的 Node,它继承 DefaultNode,用于维护一颗树,从根节点到每个叶子节点都是不同请求的调用链路,所经过的每个节点都对应着调用链路上被 Sentinel 保护的资源,一个请求调用链路上的节点顺序正是资源被访问的顺序。

Demo:

在一个 Web MVC 应用中,每个接口就是一个资源,Sentinel 通过 Spring MVC 拦截器拦截每个接口的入口,统一创建名为“sentinel_spring_web_context”的 Context,名称相同的 Context 都使用同一个 EntranceNode。一个 Web 应用可能有多个接口,而 childList 就用于存储每个接口对应的 DefaultNode。

如果想统计一个应用的所有接口(不一定是所有,没有被调用过的接口不会创建对应的 DefaultNode)总的 QPS,只需要调用 EntranceNode 的 totalQps 就能获取到。EntranceNode 的 totalQps 方法代码如下:

 

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);
    }
    
    @Override
    public double totalQps() {
        double r = 0;
        for (Node node : getChildList()) {
            r += node.totalQps();
        }
        return r;
    }    
     ***
}

 

Sentinel 中的 Context 与 Entry

Context

Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context 名称即为调用链路入口名称。

Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f) 来变换 context。

//This class holds metadata of current invocation
public class Context {
    private final String name;
    private DefaultNode entranceNode;
    private Entry curEntry;
    private String origin = "";
}

 

  • name:Context 的名称。
  • entranceNode:当前调用树的入口节点,类型为 EntranceNode。同一个入口的资源,每个资源对应一个 DefaultNode,entranceNode#childList 用于存储这些资源的 DefaultNode。
  • curEntry:当前 Entry(CtEntry),用于操作当前的node节点
  • origin:调用来源的名称,即服务消费者的名称或者服务消费者的来源 IP,取决于服务消费者是否使用 Sentinel,由 Sentinel 适配层传递过来。例如:服务提供者是 Spring MVC 应用,且服务提供者使用 Sentinel 的 Web MVC 适配,那么 Sentinel 会尝试从请求头获取"S-user",如果服务消费者有在请求头传递这个参数,那么就能够获取到。

 

Entry(CtEntry)

  1. 在调用 Context#getCurNode 方法获取调用链路上当前访问到的资源的 DefaultNode 时,实际是从 Context#curEntry 获取的,Entry 维护了当前资源的 DefaultNode,以及调用来源的 StatisticNode。
  2. 每一次资源调用都会创建一个 EntryEntry 包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。
  3. CtEntry 为普通的 Entry,在调用 SphU.entry(xxx) 的时候创建。特性:Linked entry within current context(内部维护着 parent 和 child
  4. 需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。
  5. 资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。

 

//com.alibaba.csp.sentinel.context.Context#getCurNode
public Node getCurNode() {
    return curEntry == null ? null : curEntry.getCurNode();
}

public abstract class Entry implements AutoCloseable {
    private static final Object[] OBJECTS0 = new Object[0];
    private long createTime;
    // 当前节点(DefaultNode)
    private Node curNode;
    // 来源节点
    private Node originNode;
    private Throwable error;
    // 资源
    protected ResourceWrapper resourceWrapper;
}
class CtEntry extends Entry { // 当前 Entry 指向的父 Entry protected Entry parent = null; // 父 Entry 指向当前 Entry protected Entry child = null; // 当前资源的 ProcessorSlotChain protected ProcessorSlot<Object> chain; // 当前上下文 protected Context context; }

 

CtEntry 用于维护父子 Entry,每一次调用 SphU#entry 方法都会创建一个 CtEntry。如果服务 B 在处理一个请求的路径上会多次调用 SphU#entry,那么这些 CtEntry 会构成一个双向链表。在每次创建 CtEntry,都会将 Context.curEntry 设置为这个新的 CtEntry,双向链表的作用就是在调用 CtEntry#exit 方法时,能够将 Context.curEntry 还原为上一个资源的 CtEntry。

 

几种 Node 的维度(数目):

  • ClusterNode 的维度是 resource
  • DefaultNode 的维度是 resource * context,存在每个 NodeSelectorSlot 的 map 里面
  • EntranceNode 的维度是 context,存在 ContextUtil 类的 contextNameNodeMap 里面
  • 来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面

 

posted @ 2021-11-02 15:14  龘人上天  阅读(132)  评论(0编辑  收藏  举报